├── .github
├── FUNDING.yml
└── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── chrome
├── icons
│ ├── icon128.png
│ ├── icon16.png
│ └── icon48.png
├── bump-version.sh
└── manifest.json
├── .babelrc
├── .prettierrc.js
├── .gitignore
├── .release-it.yml
├── src
├── misc
│ ├── bg.js
│ ├── helper.js
│ ├── Interactions.js
│ ├── insert-css.js
│ └── api.js
├── automations
│ ├── Anonymous.js
│ ├── Instagram.js
│ ├── HideUnanswered.js
│ ├── Swiper.js
│ └── Messenger.js
├── index.js
└── views
│ ├── Sidebar.js
│ └── templates.js
├── webpack.config.js
├── .eslintrc.js
├── package.json
└── README.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [Lunars]
4 |
--------------------------------------------------------------------------------
/chrome/icons/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Geczy/tinder-autopilot/HEAD/chrome/icons/icon128.png
--------------------------------------------------------------------------------
/chrome/icons/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Geczy/tinder-autopilot/HEAD/chrome/icons/icon16.png
--------------------------------------------------------------------------------
/chrome/icons/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Geczy/tinder-autopilot/HEAD/chrome/icons/icon48.png
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["@babel/plugin-transform-runtime", "@babel/plugin-proposal-class-properties"]
3 | }
4 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 100,
3 | singleQuote: true,
4 | trailingComma: 'none'
5 | };
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # IntelliJ project files
2 | .idea
3 | *.iml
4 | out
5 | gen
6 | node_modules
7 | yarn-error.log
8 | dist
9 | .env
10 | zips
11 |
--------------------------------------------------------------------------------
/.release-it.yml:
--------------------------------------------------------------------------------
1 | npm:
2 | publish: false
3 | hooks:
4 | after:bump:
5 | - mkdir -p zips
6 | - rm -f zips/tinder-autopilot-v*.zip || true
7 | - bash chrome/bump-version.sh ${version}
8 | - yarn build
9 | - cd dist/ && bestzip tinder-autopilot-v${version}.zip * && mv tinder-autopilot-v* ../zips/ && cd ..
10 | after:release: mkdir -p zips && rm -f zips/tinder-autopilot-v*.zip || true
11 | git:
12 | requireCleanWorkingDir: false
13 | github:
14 | assets:
15 | - zips/tinder-autopilot-v*.zip
16 | release: true
17 |
--------------------------------------------------------------------------------
/src/misc/bg.js:
--------------------------------------------------------------------------------
1 | chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
2 | fetch(request.url, request.options).then(
3 | function (response) {
4 | return response.text().then(function (text) {
5 | sendResponse([
6 | {
7 | body: text,
8 | status: response.status,
9 | statusText: response.statusText
10 | },
11 | null
12 | ]);
13 | });
14 | },
15 | function (error) {
16 | sendResponse([null, error]);
17 | }
18 | );
19 | return true;
20 | });
21 |
--------------------------------------------------------------------------------
/src/automations/Anonymous.js:
--------------------------------------------------------------------------------
1 | import { insertCss, removeCss } from '../misc/insert-css';
2 |
3 | class Anonymous {
4 | selector = '.tinderAutopilotAnonymous';
5 |
6 | start = () => {
7 | insertCss(
8 | `.messageListItem [aria-label],
9 | .Expand[aria-label] {
10 | filter: blur(3px);
11 | }
12 |
13 | .profileCard__slider__img,
14 | .StretchedBox[aria-label] {
15 | filter: blur(20px);
16 | }`
17 | );
18 | };
19 |
20 | stop = () => {
21 | removeCss();
22 | };
23 | }
24 |
25 | export default Anonymous;
26 |
--------------------------------------------------------------------------------
/.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 | **Is your feature request related to a problem? Please describe.**
10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11 |
12 | **Describe the solution you'd like**
13 | A clear and concise description of what you want to happen.
14 |
15 | **Describe alternatives you've considered**
16 | A clear and concise description of any alternative solutions or features you've considered.
17 |
18 | **Additional context**
19 | Add any other context or screenshots about the feature request here.
20 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const CopyPlugin = require('copy-webpack-plugin');
2 |
3 | module.exports = {
4 | plugins: [
5 | new CopyPlugin({
6 | patterns: [
7 | { from: 'chrome/icons', to: 'icons' },
8 | { from: 'chrome/manifest.json', to: 'manifest.json' },
9 | { from: 'src/misc/bg.js', to: 'bg.js' }
10 | ]
11 | })
12 | ],
13 | module: {
14 | rules: [
15 | {
16 | test: /\.(js|jsx)$/,
17 | exclude: /node_modules/,
18 | use: {
19 | loader: 'babel-loader'
20 | }
21 | }
22 | ]
23 | },
24 | output: {
25 | chunkLoading: false,
26 | wasmLoading: false
27 | },
28 | target: ['web', 'es5']
29 | };
30 |
--------------------------------------------------------------------------------
/chrome/bump-version.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # This script updates the "version" field in the "chrome/manifest.json" file.
4 |
5 | # Define the file path for the manifest.
6 | manifestPath="chrome/manifest.json"
7 |
8 | # Define the function to update the version field in the manifest.
9 | updateVersion() {
10 | # Use jq to update the version field with the specified version number.
11 | jq ".version = \"$1\"" $manifestPath >tmp_manifest.json
12 | mv tmp_manifest.json $manifestPath
13 | }
14 |
15 | # Check if the version argument was provided.
16 | if [[ -z $1 ]]; then
17 | echo "Error: Version number argument is required."
18 | exit 1
19 | fi
20 |
21 | # Update the version field in the manifest.
22 | updateVersion "$1"
23 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['airbnb', 'plugin:prettier/recommended', 'prettier/react'],
3 | parser: 'babel-eslint',
4 | env: {
5 | browser: true,
6 | commonjs: true,
7 | es6: true,
8 | jest: true,
9 | node: true
10 | },
11 | rules: {
12 | 'arrow-body-style': 'off',
13 | 'consistent-return': 'off',
14 | 'jsx-a11y/href-no-hash': ['off'],
15 | 'react/jsx-filename-extension': ['warn', { extensions: ['.js', '.jsx'] }],
16 | 'max-len': [
17 | 'warn',
18 | {
19 | code: 100,
20 | tabWidth: 2,
21 | comments: 100,
22 | ignoreComments: false,
23 | ignoreTrailingComments: true,
24 | ignoreUrls: true,
25 | ignoreStrings: true,
26 | ignoreTemplateLiterals: true,
27 | ignoreRegExpLiterals: true
28 | }
29 | ]
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/chrome/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Autopilot for Tinder",
3 | "short_name": "Tinder Auto",
4 | "homepage_url": "https://github.com/Geczy/tinder-autopilot/",
5 | "icons": {
6 | "16": "icons/icon16.png",
7 | "48": "icons/icon48.png",
8 | "128": "icons/icon128.png"
9 | },
10 | "description": "Don't waste any more time doing manual tasks on Tinder. Autopilot will do it for you.",
11 | "manifest_version": 2,
12 | "update_url": "https://clients2.google.com/service/update2/crx",
13 | "version": "3.0.0",
14 | "author": "Geczy",
15 | "background": {
16 | "scripts": [
17 | "bg.js"
18 | ],
19 | "persistent": false
20 | },
21 | "content_scripts": [
22 | {
23 | "js": [
24 | "main.js"
25 | ],
26 | "matches": [
27 | "https://tinder.com/*",
28 | "https://*.gotinder.com/*"
29 | ]
30 | }
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/.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 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 |
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 |
28 | - OS: [e.g. iOS]
29 | - Browser [e.g. chrome, safari]
30 | - Version [e.g. 22]
31 |
32 | **Smartphone (please complete the following information):**
33 |
34 | - Device: [e.g. iPhone6]
35 | - OS: [e.g. iOS8.1]
36 | - Browser [e.g. stock browser, safari]
37 | - Version [e.g. 22]
38 |
39 | **Additional context**
40 | Add any other context about the problem here.
41 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import get from 'lodash/get';
2 | import { logger } from './misc/helper';
3 | import Sidebar from './views/Sidebar';
4 | import { getMyProfile } from './misc/api';
5 | import Instagram from './automations/Instagram';
6 |
7 | class TinderAssistant {
8 | boostRemaining = false;
9 |
10 | isBoosting = false;
11 |
12 | constructor() {
13 | getMyProfile().then((profileData) => {
14 | const d = new Date();
15 | const n = d.getTime();
16 |
17 | const expires = get(profileData, 'data.boost.expires_at');
18 | this.boostRemaining = get(profileData, 'data.boost.allotment_remaining');
19 | const boostMinutesLeft = Math.round(expires - n) / 1000 / 60;
20 | if (boostMinutesLeft > 0) {
21 | this.isBoosting = true;
22 | }
23 |
24 | localStorage.setItem('TinderAutopilot/ProfileData', JSON.stringify(profileData));
25 |
26 | const sidebar = new Sidebar();
27 | const instagram = new Instagram();
28 | logger('Welcome to Tinder Autopilot');
29 | });
30 | }
31 | }
32 |
33 | setTimeout(() => {
34 | const Tinder = new TinderAssistant();
35 | }, 500);
36 |
--------------------------------------------------------------------------------
/src/misc/helper.js:
--------------------------------------------------------------------------------
1 | const generateRandomNumber = (min = 800, max = 1500) => {
2 | return Math.random() * (max - min) + min;
3 | };
4 |
5 | const randomDelay = async () => {
6 | const rand = generateRandomNumber(350, 600);
7 | return new Promise((resolve) => setTimeout(resolve, rand));
8 | };
9 |
10 | const logger = (v) => {
11 | console.log(v);
12 | const now = new Date();
13 | const txt = document.querySelector('.txt');
14 | const message = /* html */ `
15 | ${`0${now.getHours()}`.slice(-2)}:${`0${now.getMinutes()}`.slice(
16 | -2
17 | )}:${`0${now.getSeconds()}`.slice(-2)}.
18 | ${v}
`;
19 | txt.innerHTML = message + txt.innerHTML;
20 | };
21 |
22 | const waitUntilElementExists = (selector, callback) => {
23 | const el = document.querySelector(selector);
24 | if (el) {
25 | callback(el);
26 | }
27 | setTimeout(() => waitUntilElementExists(selector, callback), 500);
28 | };
29 |
30 | export { logger, randomDelay, generateRandomNumber, waitUntilElementExists };
31 |
--------------------------------------------------------------------------------
/src/automations/Instagram.js:
--------------------------------------------------------------------------------
1 | import { insertCss } from '../misc/insert-css';
2 |
3 | class Instagram {
4 | imageSelector = `${this.modalSelector} div[style*="instagram"]`;
5 |
6 | observer;
7 |
8 | constructor() {
9 | return;
10 | insertCss(`
11 | #modal-manager div[style*="instagram"] { cursor: zoom-in; }
12 | #modal-manager div[style*="instagram"]:hover {
13 | border: 3px solid #40a9ff;
14 | }
15 | `);
16 |
17 | try {
18 | const target = document.querySelector('[role="dialog"]').parentElement.parentElement;
19 | const observer = new MutationObserver(this.start);
20 | observer.observe(target, { childList: true });
21 |
22 | this.observer = observer;
23 | } catch (e) {}
24 | }
25 |
26 | start = () => {
27 | setTimeout(() => {
28 | document.querySelectorAll(this.imageSelector).forEach((ig) => {
29 | const url = ig.style.backgroundImage.slice(4, -1).replace(/"/g, '');
30 | ig.addEventListener('click', () => {
31 | window.open(url);
32 | });
33 | });
34 | }, 500);
35 | };
36 |
37 | stop = () => {
38 | this.observer.disconnect();
39 | };
40 | }
41 |
42 | export default Instagram;
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tinder-autopilot",
3 | "version": "3.0.0",
4 | "description": "Don't waste any more time doing manual tasks on Tinder. Autopilot will do it for you.",
5 | "main": "src/index.js",
6 | "repository": "https://github.com/Geczy/tinder-autopilot.git",
7 | "author": "Matthew ",
8 | "license": "MIT",
9 | "scripts": {
10 | "major": "dotenv release-it -- major --ci",
11 | "minor": "dotenv release-it -- minor --ci",
12 | "patch": "dotenv release-it -- patch --ci",
13 | "start": "webpack --mode development --watch",
14 | "build": "webpack --mode production"
15 | },
16 | "dependencies": {
17 | "lodash": "^4.17.21"
18 | },
19 | "devDependencies": {
20 | "@babel/core": "^7.12.13",
21 | "@babel/plugin-proposal-class-properties": "^7.12.13",
22 | "@babel/plugin-transform-runtime": "^7.12.15",
23 | "@babel/runtime": "^7.12.13",
24 | "babel-eslint": "^10.1.0",
25 | "babel-loader": "^8.2.2",
26 | "bestzip": "^2.2.0",
27 | "copy-webpack-plugin": "^9.0.1",
28 | "dotenv-cli": "^4.0.0",
29 | "eslint": "^7.19.0",
30 | "eslint-config-airbnb": "^18.2.1",
31 | "eslint-config-prettier": "^8.3.0",
32 | "eslint-plugin-import": "^2.24.2",
33 | "eslint-plugin-jsx-a11y": "^6.4.1",
34 | "eslint-plugin-prettier": "^4.0.0",
35 | "eslint-plugin-react": "^7.22.0",
36 | "eslint-plugin-react-hooks": "4.2.0",
37 | "invariant": "^2.2.4",
38 | "prettier": "^2.4.1",
39 | "release-it": "^14.3.0",
40 | "webpack": "^5.76.0",
41 | "webpack-cli": "^4.5.0"
42 | },
43 | "browserslist": [
44 | "defaults",
45 | "not IE 11",
46 | "maintained node versions"
47 | ]
48 | }
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Tinder Autopilot 🔥
2 | ·
3 | 
4 | 
5 | =====
6 |
7 | > Don't waste any more time doing manual tasks on Tinder. Autopilot will do it for you.
8 |
9 | [Install on the Chrome Web Store](https://chrome.google.com/webstore/detail/autopilot-for-tinder/bfpgbjekakfijondlfloonhdkcjlhehg?hl=en)
10 |
11 |
12 |
13 |
14 |
15 | ## Key features
16 |
17 | - 🔥 Auto liker in random intervals
18 | - 🤫 Only show unanswered messages
19 | - 🖼 Anonymous mode to blur profile images
20 | - 💌 Bulk send messages to all your matches, or just the new matches
21 | - [Missing a feature? Click to make request](https://github.com/Geczy/tinder-autopilot/issues/new)
22 |
23 | ## Contributing
24 |
25 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.
26 |
27 | 1. [Fork](https://github.com/Geczy/tinder-autopilot/fork) this repo
28 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
29 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
30 | 4. Push to the Branch (`git push origin feature/AmazingFeature`)
31 | 5. Open a Pull Request
32 |
33 | ## Development
34 |
35 | 1. Clone this repo
36 | 2. Run these commands
37 |
38 | ```sh
39 | yarn install
40 | yarn run start
41 | ```
42 |
43 | 3. Open `chrome://extensions/` in your browser
44 | 4. Click **Load unpacked** and point to the `dist` folder of this repo
45 |
46 | ## Author
47 |
48 | 👤 **Geczy **
49 |
50 | ## Show your support
51 |
52 | Give a ⭐️ if this project helped you! Maybe even a sponsorship 😍
53 |
--------------------------------------------------------------------------------
/src/misc/Interactions.js:
--------------------------------------------------------------------------------
1 | import { logger } from './helper';
2 |
3 | class Interactions {
4 | isOnMatchesPage = () => {
5 | return (
6 | window.location.toString().indexOf('app/recs') !== -1 ||
7 | window.location.toString().indexOf('app/matches') !== -1
8 | );
9 | };
10 |
11 | goToMainPage = () => {
12 | const matchesLink = document.querySelectorAll("a[href='/app/matches']");
13 | if (matchesLink && matchesLink.length) {
14 | return matchesLink[0].click();
15 | }
16 |
17 | const mainMenuLink = document.querySelectorAll("a[href='/app/recs']");
18 | if (mainMenuLink && mainMenuLink.length) {
19 | return mainMenuLink[0].click();
20 | }
21 |
22 | const matchesTab = document.querySelector('nav div:nth-child(1) > span');
23 | if (matchesTab) {
24 | return matchesTab.click();
25 | }
26 | };
27 |
28 | closeInstructions = () => {
29 | // Homescreen modal blocks us
30 | try {
31 | if (document.querySelector('[data-testid="addToHomeScreen"]')) {
32 | document
33 | .querySelector('[data-testid="addToHomeScreen"]')
34 | .parentElement.querySelector('button:nth-of-type(2)')
35 | .click();
36 | logger('Closing add to homescreen modal');
37 | return true;
38 | }
39 | } catch (e) {
40 | return false;
41 | }
42 | };
43 |
44 | closeMatchFound = () => {
45 | try {
46 | const modal = document.querySelector('[title="Back to Tinder"]');
47 | if (modal) {
48 | modal.click();
49 | logger('Closing match found');
50 | return true;
51 | }
52 | } catch (e) {
53 | return false;
54 | }
55 | };
56 |
57 | closeModal = () => {
58 | try {
59 | const modal = document.querySelector('[role="dialog"]').parentElement.parentElement;
60 | if (modal) {
61 | document.querySelector('[role="dialog"]').parentElement.click();
62 | logger('Closing modal');
63 | return true;
64 | }
65 | } catch (e) {
66 | return false;
67 | }
68 | };
69 | }
70 |
71 | export default Interactions;
72 |
--------------------------------------------------------------------------------
/src/misc/insert-css.js:
--------------------------------------------------------------------------------
1 | const containers = []; // will store container HTMLElement references
2 | const styleElements = []; // will store {prepend: HTMLElement, append: HTMLElement}
3 |
4 | const usage =
5 | 'insert-css: You need to provide a CSS string. Usage: insertCss(cssString[, options]).';
6 |
7 | function removeCss() {
8 | try {
9 | document.getElementById('TinderAutopilot-insert-css').remove();
10 | } catch {}
11 | }
12 |
13 | function insertCss(css, options) {
14 | options = options || {};
15 |
16 | if (css === undefined) {
17 | throw new Error(usage);
18 | }
19 |
20 | const position = options.prepend === true ? 'prepend' : 'append';
21 | const container =
22 | options.container !== undefined ? options.container : document.querySelector('head');
23 | let containerId = containers.indexOf(container);
24 |
25 | // first time we see this container, create the necessary entries
26 | if (containerId === -1) {
27 | containerId = containers.push(container) - 1;
28 | styleElements[containerId] = {};
29 | }
30 |
31 | // try to get the correponding container + position styleElement, create it otherwise
32 | removeCss();
33 | const styleElement = (styleElements[containerId][position] = createStyleElement());
34 |
35 | if (position === 'prepend') {
36 | container.insertBefore(styleElement, container.childNodes[0]);
37 | } else {
38 | container.appendChild(styleElement);
39 | }
40 |
41 | // strip potential UTF-8 BOM if css was read from a file
42 | if (css.charCodeAt(0) === 0xfeff) {
43 | css = css.substr(1, css.length);
44 | }
45 |
46 | // actually add the stylesheet
47 | if (styleElement.styleSheet) {
48 | styleElement.styleSheet.cssText += css;
49 | } else {
50 | styleElement.textContent += css;
51 | }
52 |
53 | return styleElement;
54 | }
55 |
56 | function createStyleElement() {
57 | const styleElement = document.createElement('style');
58 | styleElement.id = 'TinderAutopilot-insert-css';
59 | styleElement.setAttribute('type', 'text/css');
60 | return styleElement;
61 | }
62 |
63 | module.exports = insertCss;
64 | module.exports.insertCss = insertCss;
65 | module.exports.removeCss = removeCss;
66 |
--------------------------------------------------------------------------------
/src/automations/HideUnanswered.js:
--------------------------------------------------------------------------------
1 | import { logger } from '../misc/helper';
2 |
3 | class HideUnanswered {
4 | selector = '.tinderAutopilotHideMine';
5 |
6 | totalMessages = 0;
7 |
8 | counter = 0;
9 |
10 | finishHiding = () => {
11 | document.querySelectorAll('.messageListItem__message svg').forEach((t) => {
12 | const messageItem = t.closest('.messageListItem');
13 | const checkmarkIcon = messageItem.querySelector('svg[aria-label="Message Sent"]');
14 | const replyMessage = messageItem.querySelector('.messageListItem__message:last-child');
15 |
16 | if (!checkmarkIcon && !replyMessage) {
17 | // Hide the message item if it doesn't contain a checkmark icon and there is no reply message
18 | messageItem.style.display = 'none';
19 | }
20 | });
21 |
22 | const unansweredCount = Array.prototype.slice
23 | .call(document.querySelectorAll('.messageListItem'))
24 | .filter((item) => item.style.display !== 'none').length;
25 |
26 | // Scroll back to top of messages
27 | document.querySelector('.matchListTitle').parentElement.scrollTop = 0;
28 |
29 | logger(`Total matches that need a response: ${unansweredCount}`);
30 | };
31 |
32 |
33 |
34 | scrollMatchesToEnd = (cb) => {
35 | const currHeight = document.querySelector('.matchListTitle').parentElement.scrollTop;
36 | const totalHeight = document.querySelector('.matchListTitle').parentElement.scrollHeight;
37 | const newTotal = document.querySelector('div.messageList').children.length;
38 |
39 | if (this.counter < 30 && currHeight < totalHeight) {
40 | this.counter += 1;
41 | document.querySelector('.matchListTitle').parentElement.scrollTop += window.outerHeight;
42 | setTimeout(() => this.scrollMatchesToEnd(cb), 100);
43 | } else {
44 | logger(`Finished scrolling, total matches found: ${newTotal}`);
45 | cb();
46 | }
47 |
48 | if (newTotal > this.totalMessages) {
49 | this.counter = 0;
50 | }
51 |
52 | this.totalMessages = newTotal;
53 | };
54 |
55 | start = () => {
56 | if (document.querySelector('#messages-tab')) {
57 | document.querySelector('#messages-tab').click();
58 | } else {
59 | document.querySelector('a[href="/app/recs"]').click();
60 | }
61 |
62 | this.totalMessages = document.querySelector('div.messageList').children.length;
63 | this.counter = 0;
64 |
65 | this.scrollMatchesToEnd(this.finishHiding);
66 | };
67 |
68 | stop = () => {
69 | document.querySelectorAll('.messageListItem__message svg').forEach((t) => {
70 | t.closest('.messageListItem').style.display = 'flex';
71 | });
72 | };
73 | }
74 |
75 | export default HideUnanswered;
76 |
--------------------------------------------------------------------------------
/src/automations/Swiper.js:
--------------------------------------------------------------------------------
1 | import { logger, generateRandomNumber } from '../misc/helper';
2 | import Interactions from '../misc/Interactions';
3 |
4 | class Swiper {
5 | selector = '.tinderAutopilot';
6 |
7 | isRunning = false;
8 |
9 | constructor() {
10 | this.interactions = new Interactions();
11 | }
12 |
13 | start = () => {
14 | logger('Starting to swipe using a randomized interval');
15 | this.isRunning = true;
16 | this.run();
17 | };
18 |
19 | stop = () => {
20 | this.isRunning = false;
21 | logger('Autopilot stopped ⛔️');
22 | };
23 |
24 | canSwipe = () => {
25 | return this.hasLike() && !document.querySelector('.beacon__circle');
26 | };
27 |
28 | hasLike = () => {
29 | const xpath = "//span[text()='Like']";
30 | const matchingElement = document.evaluate(
31 | xpath,
32 | document,
33 | null,
34 | XPathResult.FIRST_ORDERED_NODE_TYPE,
35 | null
36 | ).singleNodeValue;
37 | return matchingElement.closest('button');
38 | };
39 |
40 | pressLike = () => {
41 | const likeButton = this.hasLike();
42 | if (!likeButton && !this.canSwipe()) {
43 | return false;
44 | }
45 |
46 | likeButton.click();
47 | document.getElementById('likeCount').innerHTML =
48 | parseInt(document.getElementById('likeCount').innerHTML, 10) + 1;
49 | return true;
50 | };
51 |
52 | matchFound = () => {
53 | const found = document.querySelectorAll('button[aria-label="Close"]');
54 |
55 | if (typeof found?.click !== 'function') {
56 | return false;
57 | }
58 |
59 | document.getElementById('matchCount').innerHTML =
60 | parseInt(document.getElementById('matchCount').innerHTML, 10) + 1;
61 | logger("Congrats! We've got a match! 🤡");
62 | found.click();
63 | return true;
64 | };
65 |
66 | run = () => {
67 | if (!this.isRunning) {
68 | return;
69 | }
70 |
71 | // Must be on matches page
72 | if (!this.interactions.isOnMatchesPage()) {
73 | logger('Going to main page to start liking');
74 | this.interactions.goToMainPage();
75 |
76 | const waitForMatchPage = setInterval(() => {
77 | if (this.interactions.isOnMatchesPage()) {
78 | clearInterval(waitForMatchPage);
79 | setTimeout(this.run, generateRandomNumber());
80 | }
81 | }, 250);
82 | }
83 |
84 | if (this.interactions.closeInstructions()) {
85 | setTimeout(this.run, generateRandomNumber());
86 | return;
87 | }
88 |
89 | if (this.interactions.closeModal()) {
90 | setTimeout(this.run, generateRandomNumber());
91 | return;
92 | }
93 |
94 | if (this.interactions.closeMatchFound()) {
95 | setTimeout(this.run, generateRandomNumber());
96 | return;
97 | }
98 |
99 | if (!this.canSwipe()) {
100 | logger('No profiles found. Waiting 4s');
101 | setTimeout(this.run, generateRandomNumber(3000, 4000));
102 | return;
103 | }
104 |
105 | // Keep Swiping
106 | if (this.matchFound()) {
107 | setTimeout(this.run, generateRandomNumber(500, 900));
108 | return;
109 | }
110 |
111 | // What we came here to do, swipe right!
112 | if (this.pressLike()) {
113 | setTimeout(this.run, generateRandomNumber(500, 900));
114 | return;
115 | }
116 |
117 | logger('No profiles found. Waiting 4s');
118 | setTimeout(this.run, generateRandomNumber(3000, 4000));
119 | };
120 | }
121 |
122 | export default Swiper;
123 |
--------------------------------------------------------------------------------
/src/automations/Messenger.js:
--------------------------------------------------------------------------------
1 | import get from 'lodash/get';
2 | import keyBy from 'lodash/keyBy';
3 | import { sendMessageToMatch, getMessagesForMatch, getMatches } from '../misc/api';
4 | import { randomDelay, logger } from '../misc/helper';
5 | import { getCheckboxValue, toggleCheckbox } from '../views/Sidebar';
6 |
7 | class Messenger {
8 | selector = '.tinderAutopilotMessage';
9 |
10 | newSelector = '.tinderAutopilotMessageNewOnly';
11 |
12 | nextPageToken;
13 |
14 | isRunningMessage;
15 |
16 | allMatches = [];
17 |
18 | checkedMessage = 0;
19 |
20 | loopMatches = async () => {
21 | const response = await getMatches(getCheckboxValue(this.newSelector), this.nextPageToken);
22 | this.nextPageToken = get(response, 'data.next_page_token');
23 | this.allMatches.push.apply(this.allMatches, get(response, 'data.matches', []));
24 | };
25 |
26 | start = () => {
27 | this.checkedMessage = 0;
28 | logger('Starting messages');
29 | this.isRunningMessage = true;
30 | this.nextPageToken = true;
31 | this.runMessage();
32 | };
33 |
34 | stop = () => {
35 | setTimeout(() => {
36 | logger('Messaging stopped ⛔️');
37 | this.isRunningMessage = false;
38 | toggleCheckbox(this.selector);
39 | }, 500);
40 | };
41 |
42 | runMessage = async () => {
43 | await this.loopMatches();
44 | while (this.nextPageToken) {
45 | logger(`Currently have ${this.allMatches.length} matches`);
46 | await this.loopMatches();
47 | }
48 |
49 | logger(`Retrieved all match history: ${this.allMatches.length}`);
50 |
51 | // To start with old matches we can reverse the array
52 | // this.allMatches = this.allMatches.reverse();
53 |
54 | logger(`Looking for matches we have not sent yet to`);
55 | this.sendMessagesTo(this.allMatches.reverse());
56 | };
57 |
58 | sendMessagesTo = async (r) => {
59 | const matchList = keyBy(r, 'id');
60 | const pendingPromiseList = [];
61 |
62 | for (const matchID of Object.keys(matchList)) {
63 | await randomDelay();
64 | if (!this.isRunningMessage) break;
65 |
66 | const match = matchList[matchID];
67 | const messageToSend = get(document.getElementById('messageToSend'), 'value', '').replace(
68 | '{name}',
69 | get(match, 'person.name').toLowerCase()
70 | );
71 |
72 | const messageToSendL = messageToSend
73 | .trim()
74 | .toLowerCase()
75 | .replace(/[^a-zA-Z0-9]+/g, '-')
76 | .replace('thanks', 'thank');
77 |
78 | pendingPromiseList.push(
79 | getMessagesForMatch(match.id)
80 | .then((messageList) => {
81 | this.checkedMessage += 1;
82 | logger(`Checked ${this.checkedMessage}/${this.allMatches.length}`);
83 | return messageList ? !messageList.includes(messageToSendL) : false;
84 | })
85 | .then((shouldSend) => {
86 | if (shouldSend) {
87 | sendMessageToMatch(match.id, { message: messageToSend }).then((b) => {
88 | if (get(b, 'sent_date')) {
89 | logger(`Message sent to ${get(match, 'person.name')}`);
90 | }
91 | });
92 | }
93 | })
94 | );
95 | }
96 |
97 | if (pendingPromiseList.length === 0) {
98 | logger('No more matches to send message to');
99 | this.stop();
100 | } else {
101 | Promise.all(pendingPromiseList).then((r) => {
102 | logger('No more matches to send message to');
103 | this.stop();
104 | });
105 | }
106 | };
107 | }
108 |
109 | export default Messenger;
--------------------------------------------------------------------------------
/src/views/Sidebar.js:
--------------------------------------------------------------------------------
1 | import {
2 | onToggle,
3 | offToggle,
4 | topBanner,
5 | autopilot,
6 | infoBanner,
7 | massMessage,
8 | loggerHeader,
9 | counterLogs,
10 | offToggleInner,
11 | onToggleInner
12 | } from './templates';
13 | import Messenger from '../automations/Messenger';
14 | import Swiper from '../automations/Swiper';
15 | import HideUnanswered from '../automations/HideUnanswered';
16 | import Anonymous from '../automations/Anonymous';
17 | import { waitUntilElementExists } from '../misc/helper';
18 |
19 | class Sidebar {
20 | constructor() {
21 | this.sidebar();
22 |
23 | this.anonymous = new Anonymous();
24 | this.hideUnanswered = new HideUnanswered();
25 | this.swiper = new Swiper();
26 | this.messenger = new Messenger();
27 |
28 | this.events();
29 | }
30 |
31 | insertBefore = (el, referenceNode) => {
32 | referenceNode.parentNode.insertBefore(el, referenceNode);
33 | };
34 |
35 | sidebar = () => {
36 | const el = document.createElement('aside');
37 | el.className = 'H(100%) Fld(c) Pos(r) Flxg(0) Fxs(0) Flxb(25%) Miw(325px) Maw(375px)';
38 | el.style.cssText = 'background-color:#1f2937;z-index:9999999;';
39 | el.innerHTML = infoBanner;
40 | this.insertBefore(el, document.querySelector('aside:first-of-type'));
41 |
42 | this.infoBanner = document.querySelector('#infoBanner');
43 |
44 | this.infoBanner.innerHTML =
45 | `
46 |
`;
52 | };
53 |
54 | events = () => {
55 | // Auto unmatch
56 | waitUntilElementExists('img[alt="No Reason"]', () => {
57 | document.querySelector('ul li:last-of-type button').click();
58 | document.querySelector('.modal-slide-up div button[type="button"]').click();
59 | });
60 |
61 | this.bindCheckbox(this.anonymous.selector, this.anonymous.start, this.anonymous.stop);
62 |
63 | this.bindCheckbox(this.messenger.newSelector);
64 |
65 | this.bindCheckbox(this.swiper.selector, this.swiper.start, this.swiper.stop);
66 |
67 | this.bindCheckbox(this.messenger.selector, this.messenger.start, this.messenger.stop);
68 |
69 | this.bindCheckbox(
70 | this.hideUnanswered.selector,
71 | this.hideUnanswered.start,
72 | this.hideUnanswered.stop
73 | );
74 |
75 | document.getElementById('messageToSend').addEventListener('blur', (e) => {
76 | localStorage.setItem('TinderAutopilot/MessengerDefault', JSON.stringify(e.target.value));
77 | });
78 | };
79 |
80 | bindCheckbox = (selector, start = false, stop = false) => {
81 | document.querySelector(selector).onclick = (e) => {
82 | e.preventDefault();
83 |
84 | const isOn = getCheckboxValue(selector);
85 | toggleCheckbox(selector);
86 | if (isOn && stop) stop();
87 | if (!isOn && start) start();
88 | };
89 | };
90 | }
91 |
92 | const getCheckboxValue = (selector) =>
93 | document.querySelector(`${selector} .toggleSwitch > div`).className === onToggle;
94 |
95 | const toggleCheckbox = (selector) => {
96 | const isOn = getCheckboxValue(selector);
97 | document.querySelector(`${selector} .toggleSwitch > div`).className = isOn ? offToggle : onToggle;
98 | document.querySelector(`${selector} .toggleSwitch > div > div`).className = isOn
99 | ? offToggleInner
100 | : onToggleInner;
101 | };
102 |
103 | export { getCheckboxValue, toggleCheckbox };
104 |
105 | export default Sidebar;
106 |
--------------------------------------------------------------------------------
/src/misc/api.js:
--------------------------------------------------------------------------------
1 | import get from 'lodash/get';
2 |
3 | const headers = {
4 | referrer: 'https://tinder.com/',
5 | referrerPolicy: 'origin',
6 | accept: 'application/json; charset=UTF-8',
7 | 'persistent-device-id': localStorage.getItem('TinderWeb/uuid'),
8 | platform: 'web',
9 | 'X-Auth-Token': localStorage.getItem('TinderWeb/APIToken')
10 | };
11 |
12 | const defaultOptions = {
13 | headers,
14 | method: 'GET'
15 | };
16 |
17 | const fetchResource = (url, body = false) => {
18 | return new Promise((resolve, reject) => {
19 | const options = defaultOptions;
20 | if (body) {
21 | options.headers['content-type'] = 'application/json';
22 | options.body = JSON.stringify(body);
23 | options.method = 'POST';
24 | }
25 | chrome.runtime.sendMessage({ url, options }, (messageResponse) => {
26 | const [response, error] = messageResponse;
27 | if (response === null) {
28 | reject(error);
29 | } else {
30 | // Use undefined on a 204 - No Content
31 | const body = response.body ? new Blob([response.body]) : undefined;
32 | resolve(
33 | new Response(body, {
34 | status: response.status,
35 | statusText: response.statusText
36 | })
37 | );
38 | }
39 | });
40 | })
41 | .then((response) => {
42 | return response.text();
43 | })
44 | .then((data) => {
45 | return data ? JSON.parse(data) : {};
46 | })
47 | .catch((error) => {
48 | console.log(error);
49 | });
50 | };
51 |
52 | const getMatches = async (newOnly, nextPageToken) => {
53 | return fetchResource(
54 | `https://api.gotinder.com/v2/matches?count=100&is_tinder_u=true&locale=en&message=${
55 | newOnly ? 0 : 1
56 | }${typeof nextPageToken === 'string' ? `&page_token=${nextPageToken}` : ''}`
57 | );
58 | };
59 |
60 | const getMyProfile = () =>
61 | fetchResource(
62 | `https://api.gotinder.com/v2/profile?locale=en&include=account%2Cboost%2Ccontact_cards%2Cemail_settings%2Cinstagram%2Clikes%2Cnotifications%2Cplus_control%2Cproducts%2Cpurchase%2Creadreceipts%2Cswipenote%2Cspotify%2Csuper_likes%2Ctinder_u%2Ctravel%2Ctutorials%2Cuser`
63 | );
64 |
65 | const getMessagesForMatch = ({ id }) =>
66 | fetchResource(`https://api.gotinder.com/v2/matches/${id}/messages?count=100`).then((data) =>
67 | get(data, 'data.messages', []).map((r) =>
68 | get(r, 'message', '')
69 | .trim()
70 | .toLowerCase()
71 | .replace(/[^a-zA-Z0-9]+/g, '-')
72 | .replace('thanks', 'thank')
73 | )
74 | );
75 |
76 | const getProfileData = () => {
77 | try {
78 | return JSON.parse(localStorage.getItem('TinderAutopilot/ProfileData'));
79 | } catch {
80 | return false;
81 | }
82 | };
83 |
84 | const getSnapchatUsername = () => {
85 | const profile = getProfileData();
86 | const snapAccount = get(profile, 'data.contact_cards.populated_cards', []).find(
87 | (a) => a.contact_type === 'snapchat'
88 | );
89 | return snapAccount ? snapAccount.contact_id : false;
90 | };
91 |
92 | // To send a snapchat username to a match id
93 | // sendMessageToMatch('1234', { type: 'snapchat' });
94 | const sendMessageToMatch = (matchID, options) => {
95 | const user = getSnapchatUsername();
96 |
97 | const body = options;
98 | if (options && options.type) {
99 | switch (options.type) {
100 | case 'snapchat':
101 | if (!user) throw new Error('No snapchat user exists');
102 | body.message = user;
103 | body.type = 'contact_card';
104 | body.contact_type = 'snapchat';
105 | break;
106 | default:
107 | break;
108 | }
109 | }
110 | return fetchResource(`https://api.gotinder.com/user/matches/${matchID}?locale=en`, body);
111 | };
112 |
113 | export { fetchResource, sendMessageToMatch, getMessagesForMatch, getMatches, getMyProfile };
114 |
--------------------------------------------------------------------------------
/src/views/templates.js:
--------------------------------------------------------------------------------
1 | let defaultMessage = `Hey {name}, this is an automated message to remind you of your upcoming "Netflix and Chill" appointment in the next week. To confirm your appointment text YES DADDY. To unsubscribe, please text WRONG HOLE. Standard text and bill rates do apply. Thanks for choosing Slide N Yo DMs`;
2 |
3 | const msg = JSON.parse(localStorage.getItem('TinderAutopilot/MessengerDefault'));
4 | if (msg) {
5 | defaultMessage = msg;
6 | } else {
7 | localStorage.setItem('TinderAutopilot/MessengerDefault', JSON.stringify(defaultMessage));
8 | }
9 |
10 | const onToggle = `CenterAlign Bdrs(16px) W(50px) H(30px) Bd Pe(n) Trstf(eio) Trsdu($fast) Bdc($c-pink) Bg($c-pink)`;
11 | const onToggleInner = `Bdrs(50%) Bgc(#fff) Bd Sq(28px) Trstf(eio) Trsdu($fast) Bdc($c-pink) TranslateX(10px)`;
12 |
13 | const offToggle = `CenterAlign Bdrs(16px) W(50px) H(30px) Bd Pe(n) Trstf(eio) Trsdu($fast) Bdc($c-divider) Bgc($c-divider)`;
14 | const offToggleInner = `Bdrs(50%) Bgc(#fff) Bd Sq(28px) Trstf(eio) Trsdu($fast) Bdc($c-divider) TranslateX(-10px)`;
15 |
16 | const topBanner = `
17 |
22 | `;
23 |
24 | const titleGenerator = (title) =>
25 | `${title} `;
26 |
27 | const textboxGenerator = ({ className, placeholder, helpText, defaultValue }) => `
28 |
29 |
37 |
38 | ${helpText &&
39 | `${helpText}
`
40 | }
41 | `;
42 |
43 | const checkboxGenerator = (className, label, helpText = '') => `
44 |
45 |
60 |
61 | ${helpText &&
62 | `${helpText}
`
63 | }
64 | `;
65 |
66 | const autopilot = `
67 |
68 | ${titleGenerator('Main Settings')}
69 | ${checkboxGenerator(
70 | 'tinderAutopilot',
71 | 'Auto like',
72 | 'Begin automatically swiping right on all profiles.'
73 | )}
74 | ${checkboxGenerator(
75 | 'tinderAutopilotHideMine',
76 | 'Only show unanswered messages',
77 | 'Useful if you just sent an auto message to a ton of people and only want to see the ones that responded.'
78 | )}
79 | ${checkboxGenerator(
80 | 'tinderAutopilotAnonymous',
81 | 'Anonymous Mode',
82 | 'Hide profile pictures so you can take screenshots.'
83 | )}
84 |
85 | `;
86 |
87 | const massMessage = `
88 |
89 | ${titleGenerator('Messaging Settings')}
90 | ${checkboxGenerator('tinderAutopilotMessage', 'Auto message')}
91 | ${checkboxGenerator('tinderAutopilotMessageNewOnly', 'New matches only')}
92 | ${textboxGenerator({
93 | helpText: 'The message to send to matches.',
94 | placeholder: 'Your message to send',
95 | className: 'messageToSend',
96 | defaultValue: defaultMessage
97 | })}
98 |
99 | `;
100 |
101 | const loggerHeader = `${titleGenerator('Activity')}
`;
102 |
103 | const counterLogs = (likeCount, matchCount) => `
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
${likeCount} Liked
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
${matchCount} Matched
127 |
128 |
129 |
130 | `;
131 |
132 | const infoBanner = ``;
133 |
134 | export {
135 | topBanner,
136 | autopilot,
137 | infoBanner,
138 | massMessage,
139 | loggerHeader,
140 | counterLogs,
141 | offToggle,
142 | offToggleInner,
143 | onToggle,
144 | onToggleInner
145 | };
146 |
--------------------------------------------------------------------------------