├── icon.png ├── screenshot.png ├── background.js ├── LICENSE ├── manifest.json ├── PRIVACY.md ├── README.md ├── popup.html ├── script.js ├── styles.css └── popup.js /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabireze/tiktok-all-liked-videos-remover/HEAD/icon.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabireze/tiktok-all-liked-videos-remover/HEAD/screenshot.png -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | chrome.runtime.onMessage.addListener(async function ( 2 | request, 3 | sender, 4 | sendResponse 5 | ) { 6 | if (request.action === "removeLikedVideos") { 7 | try { 8 | const tab = await chrome.tabs.create({ 9 | url: "https://www.tiktok.com/", 10 | active: true, 11 | }); 12 | 13 | chrome.tabs.onUpdated.addListener(function listener(tabId, info) { 14 | if (tabId === tab.id && info.status === "complete") { 15 | chrome.scripting.executeScript({ 16 | target: { tabId: tab.id }, 17 | files: ["script.js"], 18 | }); 19 | chrome.tabs.onUpdated.removeListener(listener); 20 | } 21 | }); 22 | } catch (error) { 23 | console.log({ 24 | message: "Error opening TikTok or starting script.", 25 | error, 26 | }); 27 | } 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Gabriel de Rezende Gonçalves 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 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TikTok All Liked Videos Remover", 3 | "description": "A Chrome extension to remove all liked videos on TikTok.", 4 | "version": "1.2.2", 5 | "author": { 6 | "name": "Gabriel de Rezende Gonçalves", 7 | "email": "contato@gabireze.com.br", 8 | "url": "https://gabireze.com.br/" 9 | }, 10 | "homepage_url": "https://chrome.google.com/webstore/detail/tiktok-all-liked-videos-r/eafmacjdgennnmhagdkdckgjokmnllci/", 11 | "permissions": ["scripting", "tabs"], 12 | "host_permissions": ["https://*.tiktok.com/*"], 13 | "background": { 14 | "service_worker": "background.js" 15 | }, 16 | "icons": { 17 | "16": "icon.png", 18 | "48": "icon.png", 19 | "128": "icon.png" 20 | }, 21 | "action": { 22 | "default_icon": { 23 | "16": "icon.png", 24 | "48": "icon.png", 25 | "128": "icon.png" 26 | }, 27 | "default_title": "TikTok All Liked Videos Remover", 28 | "default_popup": "popup.html" 29 | }, 30 | "web_accessible_resources": [ 31 | { 32 | "resources": ["script.js", "styles.css"], 33 | "matches": ["https://*.tiktok.com/*"] 34 | } 35 | ], 36 | "manifest_version": 3 37 | } 38 | -------------------------------------------------------------------------------- /PRIVACY.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | ## Overview 4 | 5 | TikTok All Liked Videos Remover is a Chrome extension designed to help users automatically unlike all their previously liked videos on TikTok. We are committed to respecting your privacy and ensuring that your personal data is never collected, stored, or shared. 6 | 7 | --- 8 | 9 | ## What Data We Collect 10 | 11 | **We do not collect or transmit any personal data.** 12 | The extension operates entirely within your browser and does not communicate with any external server. 13 | 14 | Specifically: 15 | - We do **not** collect your TikTok credentials. 16 | - We do **not** access any TikTok content beyond what is necessary to automate the unliking process. 17 | - We do **not** track your activity, behavior, or browsing history. 18 | 19 | --- 20 | 21 | ## How the Extension Works 22 | 23 | The extension performs the following steps: 24 | - Opens [tiktok.com](https://www.tiktok.com) in a new browser tab. 25 | - Navigates automatically to your TikTok profile. 26 | - Opens your “Liked” tab. 27 | - Enters each liked video and simulates a click to remove the like. 28 | 29 | All actions are performed **locally in your browser**. No data is stored, transmitted, or logged externally. 30 | 31 | --- 32 | 33 | ## Third-Party Services 34 | 35 | This extension does **not** use third-party analytics, external APIs, or tracking scripts of any kind. 36 | 37 | --- 38 | 39 | ## Permissions Explanation 40 | 41 | The extension uses the following Chrome permissions: 42 | 43 | - **`tabs`**: Opens a new TikTok tab and allows script injection to automate the removal process. 44 | - **`scripting`**: Enables the execution of the core logic that interacts with the TikTok page. 45 | - **`host_permissions`** (`https://www.tiktok.com/*`): Provides access only to TikTok pages for the purpose of automation. No other domains are accessed. 46 | 47 | These permissions are strictly required for the extension to work as intended. 48 | 49 | --- 50 | 51 | ## Contact 52 | 53 | If you have any questions, concerns, or feedback, feel free to reach out: 54 | 55 | **Developer:** Gabriel de Rezende Gonçalves 56 | **Website:** [gabireze.com.br](https://gabireze.com.br) 57 | **GitHub:** [github.com/gabireze](https://github.com/gabireze) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TikTok All Liked Videos Remover 2 | 3 | Remove all your liked videos on TikTok automatically — no clicks, no scrolling, just one button. 4 | 5 | ![Screenshot](screenshot.png) 6 | 7 | --- 8 | 9 | ## GitAds Sponsored 10 | [![Sponsored by GitAds](https://gitads.dev/v1/ad-serve?source=gabireze/tiktok-all-liked-videos-remover@github)](https://gitads.dev/v1/ad-track?source=gabireze/tiktok-all-liked-videos-remover@github) 11 | 12 | --- 13 | 14 | ## Features 15 | 16 | - ✅ Opens your TikTok profile in a new tab automatically 17 | - ✅ Accesses your "Liked" tab 18 | - ✅ Enters each liked video and removes the like 19 | - ✅ Proceeds to the next video until the list is empty 20 | - ✅ Fully automatic — just start and let it run 21 | 22 | --- 23 | 24 | ## Installation 25 | 26 | ### From Chrome Web Store 27 | 28 | 👉 [Install from Chrome Web Store](https://chrome.google.com/webstore/detail/tiktok-all-liked-videos-r/eafmacjdgennnmhagdkdckgjokmnllci) 29 | 30 | ### 🛠️ Manual Installation (for developers) 31 | 32 | 1. Clone this repository or download the source code. 33 | 2. Go to `chrome://extensions` in Google Chrome. 34 | 3. Enable **Developer mode** (top right toggle). 35 | 4. Click **"Load unpacked"** and select the project folder. 36 | 37 | --- 38 | 39 | ## How to Use 40 | 41 | 1. Make sure you're **logged in** to your TikTok account at [tiktok.com](https://tiktok.com). 42 | 2. Click the extension icon in the Chrome toolbar. 43 | 3. Click **"Start Removing Likes"**. 44 | 4. A new TikTok tab will open automatically. 45 | 5. ✅ The extension will: 46 | - Navigate to your profile 47 | - Open the "Liked" tab 48 | - Unlike each video one by one 49 | 6. Keep the tab open until the process finishes. **Do not close it** during the operation. 50 | 51 | --- 52 | 53 | ## Important Notes 54 | 55 | - The process may take time depending on how many liked videos you have. 56 | - If TikTok temporarily blocks actions (rate limiting), wait about **1 hour** and run the extension again. 57 | - To confirm everything was removed, **refresh your Liked tab** after the process completes. 58 | 59 | --- 60 | 61 | ## Contributing 62 | 63 | Contributions are welcome! 64 | If you find a bug or have an idea for improvement, feel free to open an issue or a pull request. 65 | 66 | --- 67 | 68 | ## License 69 | 70 | This project is licensed under the [MIT License](https://opensource.org/license/mit/). 71 | 72 | 73 | -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TikTok All Liked Videos Remover 6 | 7 | 11 | 15 | 16 | 17 | 18 |
19 | 27 |
28 | 35 | 57 |
58 |
59 |
60 |

61 | Before you start: Make sure you are 62 | logged in to your account at 63 | tiktok.com. 64 |

65 |

66 | Step: Click the button below. A new TikTok tab will 67 | open automatically and the extension will begin the removal process. 68 |

69 | 70 | 77 |

78 | Note: If you have many liked videos, the process may 79 | take a while. TikTok may temporarily block actions if too many happen 80 | quickly. If that occurs, wait about 1 hour and try 81 | again. 82 |

83 |

84 | Tip: When the process finishes, refresh your profile to 85 | verify the videos were unliked successfully. 86 |

87 |
88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | const initiateLikedVideosRemoval = async () => { 2 | const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); 3 | 4 | const waitForElement = async (selector, timeout = 10000, interval = 200) => { 5 | const start = Date.now(); 6 | return new Promise((resolve, reject) => { 7 | const check = () => { 8 | const element = document.querySelector(selector); 9 | if (element) return resolve(element); 10 | if (Date.now() - start >= timeout) { 11 | return reject(new Error(`Timeout: Element ${selector} not found`)); 12 | } 13 | setTimeout(check, interval); 14 | }; 15 | check(); 16 | }); 17 | }; 18 | 19 | const clickProfileTab = async () => { 20 | try { 21 | const profileButton = await waitForElement('[data-e2e="nav-profile"]'); 22 | profileButton.click(); 23 | console.log("Successfully clicked the 'Profile' button."); 24 | await sleep(5000); 25 | return true; 26 | } catch (error) { 27 | stopScript( 28 | "The 'Profile' button was not found on the page in time", 29 | error 30 | ); 31 | return false; 32 | } 33 | }; 34 | 35 | const clickLikedTab = async () => { 36 | try { 37 | const likedTab = await waitForElement('[data-e2e="liked-tab"]'); 38 | likedTab.click(); 39 | console.log("Successfully opened the 'Liked' tab."); 40 | await sleep(5000); 41 | } catch (error) { 42 | stopScript("Error clicking the 'Liked' tab", error); 43 | } 44 | }; 45 | 46 | const clickLikedVideo = async () => { 47 | try { 48 | const firstVideo = await waitForElement('[class*="DivPlayerContainer"]'); 49 | firstVideo.click(); 50 | console.log("Successfully opened the first liked video."); 51 | await sleep(5000); 52 | } catch (error) { 53 | stopScript("No liked videos found or unable to open", error); 54 | } 55 | }; 56 | 57 | const clickNextLikedVideoAndRemove = async () => { 58 | try { 59 | const interval = setInterval(async () => { 60 | const nextVideoButton = document.querySelector( 61 | '[data-e2e="arrow-right"]' 62 | ); 63 | const likeButton = document.querySelector( 64 | '[data-e2e="browse-like-icon"]' 65 | ); 66 | 67 | if (!likeButton) { 68 | clearInterval(interval); 69 | stopScript("Like button not found"); 70 | return; 71 | } 72 | 73 | likeButton.click(); 74 | console.log("Removed like from current video."); 75 | 76 | if (!nextVideoButton || nextVideoButton.disabled) { 77 | clearInterval(interval); 78 | closeVideo(); 79 | return; 80 | } 81 | 82 | nextVideoButton.click(); 83 | console.log("Moved to next liked video."); 84 | }, 2000); 85 | } catch (error) { 86 | stopScript("Error during liked video removal", error); 87 | } 88 | }; 89 | 90 | const closeVideo = async () => { 91 | try { 92 | const closeVideoButton = document.querySelector( 93 | '[data-e2e="browse-close"]' 94 | ); 95 | if (closeVideoButton) { 96 | closeVideoButton.click(); 97 | console.log("Closed video view."); 98 | stopScript("All actions executed successfully"); 99 | } else { 100 | stopScript("Could not find the close video button"); 101 | } 102 | } catch (error) { 103 | stopScript("Error closing the video", error); 104 | } 105 | }; 106 | 107 | const stopScript = (message, error = "") => { 108 | let logMessage = `${message}. Reloading page...`; 109 | if (error) { 110 | console.log({ message: logMessage, error }); 111 | } else { 112 | console.log(logMessage); 113 | } 114 | setTimeout(() => window.location.reload(), 1000); 115 | }; 116 | 117 | try { 118 | console.log("Script started..."); 119 | const wentToProfile = await clickProfileTab(); 120 | if (!wentToProfile) return; 121 | await clickLikedTab(); 122 | await clickLikedVideo(); 123 | await clickNextLikedVideoAndRemove(); 124 | } catch (error) { 125 | stopScript("Unexpected error in main flow", error); 126 | } 127 | }; 128 | 129 | initiateLikedVideosRemoval(); 130 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --color-bg: #121212; 3 | --color-surface: #1e1e1e; 4 | --color-text: #f0f0f0; 5 | --color-muted: #b0b0b0; 6 | --color-accent: #ff0050; 7 | --color-accent-alt: #00f2ea; 8 | --color-warning: #ff4d4d; 9 | } 10 | 11 | body { 12 | font-family: 'Roboto', sans-serif; 13 | margin: 0; 14 | padding: 0; 15 | background-color: var(--color-bg); 16 | color: var(--color-text); 17 | } 18 | 19 | header { 20 | display: flex; 21 | align-items: center; 22 | justify-content: space-between; 23 | background-color: var(--color-surface); 24 | padding: 14px 20px; 25 | border-bottom: 1px solid #2a2a2a; 26 | width: 400px; 27 | margin: auto; 28 | } 29 | 30 | .logo { 31 | display: flex; 32 | align-items: center; 33 | gap: 10px; 34 | text-decoration: none; 35 | color: var(--color-text); 36 | } 37 | 38 | .logo img { 39 | width: 28px; 40 | height: 28px; 41 | } 42 | 43 | .title { 44 | font-size: 15px; 45 | font-weight: bold; 46 | } 47 | 48 | .menu-wrapper { 49 | position: relative; 50 | display: inline-block; 51 | } 52 | 53 | .header-icons { 54 | display: flex; 55 | align-items: center; 56 | gap: 12px; 57 | } 58 | 59 | .donate-button { 60 | display: flex; 61 | align-items: center; 62 | cursor: pointer; 63 | color: var(--color-accent); 64 | transition: all 0.2s; 65 | padding: 4px; 66 | border-radius: 4px; 67 | } 68 | 69 | .donate-button:hover { 70 | color: var(--color-accent-alt); 71 | background-color: rgba(255, 0, 80, 0.1); 72 | transform: scale(1.05); 73 | } 74 | 75 | .donate-button .material-icons { 76 | font-size: 24px; 77 | } 78 | 79 | .menu { 80 | display: flex; 81 | align-items: center; 82 | cursor: pointer; 83 | color: var(--color-text); 84 | transition: all 0.2s; 85 | padding: 4px; 86 | border-radius: 4px; 87 | } 88 | 89 | .menu:hover { 90 | color: var(--color-accent-alt); 91 | background-color: rgba(0, 242, 234, 0.1); 92 | transform: scale(1.05); 93 | } 94 | 95 | .menu .material-icons { 96 | font-size: 24px; 97 | } 98 | 99 | .dropdown-menu { 100 | display: none; 101 | position: absolute; 102 | right: 0; 103 | background-color: var(--color-surface); 104 | border: 1px solid #2a2a2a; 105 | border-radius: 6px; 106 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); 107 | flex-direction: column; 108 | width: 240px; 109 | z-index: 10; 110 | transition: opacity 0.3s ease; 111 | } 112 | 113 | .menu-wrapper:hover .dropdown-menu { 114 | display: flex; 115 | } 116 | 117 | .dropdown-menu a { 118 | color: var(--color-text); 119 | text-decoration: none; 120 | padding: 12px 16px; 121 | font-size: 14px; 122 | transition: background 0.2s; 123 | } 124 | 125 | .dropdown-menu a:hover { 126 | background-color: #2a2a2a; 127 | } 128 | 129 | .container { 130 | margin: auto; 131 | background-color: var(--color-surface); 132 | padding: 8px 20px 16px 20px; 133 | width: 400px; 134 | box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3); 135 | display: flex; 136 | flex-direction: column; 137 | gap: 12px; 138 | } 139 | 140 | .instructions { 141 | font-size: 15px; 142 | line-height: 1.6; 143 | margin-bottom: 16px; 144 | color: var(--color-muted); 145 | } 146 | 147 | .instructions strong { 148 | color: var(--color-text); 149 | } 150 | 151 | .warning { 152 | font-size: 14px; 153 | background-color: #2a0000; 154 | color: var(--color-warning); 155 | padding: 12px 14px; 156 | border-radius: 6px; 157 | margin-bottom: 20px; 158 | line-height: 1.5; 159 | } 160 | 161 | .button { 162 | margin: 4px 0 8px 0; 163 | background: linear-gradient(90deg, var(--color-accent), var(--color-accent-alt)); 164 | color: #fff; 165 | padding: 12px; 166 | width: 100%; 167 | border: none; 168 | border-radius: 8px; 169 | font-size: 15px; 170 | font-weight: 500; 171 | cursor: pointer; 172 | transition: transform 0.2s ease; 173 | } 174 | 175 | .button:hover { 176 | transform: scale(1.03); 177 | } 178 | 179 | p { 180 | margin-top: 0px; 181 | } 182 | 183 | .info-box { 184 | font-size: 14px; 185 | margin: 0; 186 | padding: 12px 14px; 187 | border-radius: 6px; 188 | line-height: 1.5; 189 | border-left: 4px solid; 190 | } 191 | 192 | .info-login { 193 | background-color: #1e1e1e; 194 | border-color: var(--color-accent); 195 | color: var(--color-text); 196 | } 197 | 198 | .info-process { 199 | background-color: #1a1a1a; 200 | border-color: var(--color-accent-alt); 201 | color: var(--color-text); 202 | } 203 | 204 | .info-warning { 205 | background-color: #2a0000; 206 | border-color: var(--color-warning); 207 | color: var(--color-warning); 208 | } 209 | 210 | .info-check { 211 | background-color: #002a2a; 212 | border-color: var(--color-accent-alt); 213 | color: #9ff; 214 | } 215 | 216 | .info-box a { 217 | color: var(--color-accent-alt); 218 | text-decoration: underline; 219 | } 220 | 221 | .info-box ul { 222 | padding-left: 20px; 223 | } 224 | 225 | .info-list { 226 | font-size: 14px; 227 | color: var(--color-text); 228 | padding: 0px 16px; 229 | list-style-type: disc; 230 | margin: 0px; 231 | } 232 | 233 | 234 | -------------------------------------------------------------------------------- /popup.js: -------------------------------------------------------------------------------- 1 | // Mapeamento de países para códigos de moeda do PayPal 2 | const COUNTRY_CURRENCY_MAP = { 3 | US: "USD", 4 | CA: "CAD", 5 | GB: "GBP", 6 | DE: "EUR", 7 | FR: "EUR", 8 | IT: "EUR", 9 | ES: "EUR", 10 | NL: "EUR", 11 | AU: "AUD", 12 | JP: "JPY", 13 | BR: "BRL", 14 | MX: "MXN", 15 | AR: "ARS", 16 | CL: "CLP", 17 | CO: "COP", 18 | PE: "PEN", 19 | UY: "UYU", 20 | PY: "PYG", 21 | BO: "BOB", 22 | EC: "USD", 23 | VE: "VES", 24 | CR: "CRC", 25 | PA: "PAB", 26 | GT: "GTQ", 27 | HN: "HNL", 28 | SV: "USD", 29 | NI: "NIO", 30 | DO: "DOP", 31 | CU: "CUP", 32 | HT: "HTG", 33 | JM: "JMD", 34 | BS: "BSD", 35 | BB: "BBD", 36 | TT: "TTD", 37 | GY: "GYD", 38 | SR: "SRD", 39 | FK: "FKP", 40 | CH: "CHF", 41 | NO: "NOK", 42 | SE: "SEK", 43 | DK: "DKK", 44 | FI: "EUR", 45 | IE: "EUR", 46 | AT: "EUR", 47 | BE: "EUR", 48 | LU: "EUR", 49 | PT: "EUR", 50 | GR: "EUR", 51 | CY: "EUR", 52 | MT: "EUR", 53 | SK: "EUR", 54 | SI: "EUR", 55 | EE: "EUR", 56 | LV: "EUR", 57 | LT: "EUR", 58 | PL: "PLN", 59 | CZ: "CZK", 60 | HU: "HUF", 61 | RO: "RON", 62 | BG: "BGN", 63 | HR: "EUR", 64 | RS: "RSD", 65 | BA: "BAM", 66 | MK: "MKD", 67 | AL: "ALL", 68 | ME: "EUR", 69 | XK: "EUR", 70 | MD: "MDL", 71 | UA: "UAH", 72 | BY: "BYN", 73 | RU: "RUB", 74 | KZ: "KZT", 75 | UZ: "UZS", 76 | TJ: "TJS", 77 | KG: "KGS", 78 | TM: "TMT", 79 | AF: "AFN", 80 | PK: "PKR", 81 | IN: "INR", 82 | LK: "LKR", 83 | BD: "BDT", 84 | NP: "NPR", 85 | BT: "BTN", 86 | MV: "MVR", 87 | CN: "CNY", 88 | HK: "HKD", 89 | MO: "MOP", 90 | TW: "TWD", 91 | KR: "KRW", 92 | KP: "KPW", 93 | MN: "MNT", 94 | MM: "MMK", 95 | TH: "THB", 96 | LA: "LAK", 97 | KH: "KHR", 98 | VN: "VND", 99 | MY: "MYR", 100 | SG: "SGD", 101 | BN: "BND", 102 | ID: "IDR", 103 | PH: "PHP", 104 | TL: "USD", 105 | PG: "PGK", 106 | SB: "SBD", 107 | VU: "VUV", 108 | FJ: "FJD", 109 | NC: "XPF", 110 | PF: "XPF", 111 | WS: "WST", 112 | TO: "TOP", 113 | KI: "AUD", 114 | TV: "AUD", 115 | NR: "AUD", 116 | MH: "USD", 117 | FM: "USD", 118 | PW: "USD", 119 | AS: "USD", 120 | GU: "USD", 121 | MP: "USD", 122 | PR: "USD", 123 | VI: "USD", 124 | UM: "USD", 125 | NZ: "NZD", 126 | CK: "NZD", 127 | NU: "NZD", 128 | PN: "NZD", 129 | TK: "NZD", 130 | }; 131 | 132 | // Função para detectar o país do usuário usando apenas recursos do navegador 133 | function detectUserCountry() { 134 | try { 135 | // Método 1: Usar timezone do navegador (mais preciso) 136 | const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; 137 | const timezoneCountryMap = { 138 | // Americas 139 | "America/Sao_Paulo": "BR", 140 | "America/Argentina/Buenos_Aires": "AR", 141 | "America/Santiago": "CL", 142 | "America/Bogota": "CO", 143 | "America/Lima": "PE", 144 | "America/Montevideo": "UY", 145 | "America/Asuncion": "PY", 146 | "America/La_Paz": "BO", 147 | "America/Guayaquil": "EC", 148 | "America/Caracas": "VE", 149 | "America/Costa_Rica": "CR", 150 | "America/Panama": "PA", 151 | "America/Guatemala": "GT", 152 | "America/Tegucigalpa": "HN", 153 | "America/El_Salvador": "SV", 154 | "America/Managua": "NI", 155 | "America/Santo_Domingo": "DO", 156 | "America/Havana": "CU", 157 | "America/Port-au-Prince": "HT", 158 | "America/Jamaica": "JM", 159 | "America/New_York": "US", 160 | "America/Chicago": "US", 161 | "America/Denver": "US", 162 | "America/Los_Angeles": "US", 163 | "America/Anchorage": "US", 164 | "America/Toronto": "CA", 165 | "America/Vancouver": "CA", 166 | "America/Mexico_City": "MX", 167 | 168 | // Europe 169 | "Europe/London": "GB", 170 | "Europe/Dublin": "IE", 171 | "Europe/Paris": "FR", 172 | "Europe/Berlin": "DE", 173 | "Europe/Madrid": "ES", 174 | "Europe/Rome": "IT", 175 | "Europe/Amsterdam": "NL", 176 | "Europe/Brussels": "BE", 177 | "Europe/Zurich": "CH", 178 | "Europe/Vienna": "AT", 179 | "Europe/Stockholm": "SE", 180 | "Europe/Oslo": "NO", 181 | "Europe/Copenhagen": "DK", 182 | "Europe/Helsinki": "FI", 183 | "Europe/Warsaw": "PL", 184 | "Europe/Prague": "CZ", 185 | "Europe/Budapest": "HU", 186 | "Europe/Bucharest": "RO", 187 | "Europe/Sofia": "BG", 188 | "Europe/Athens": "GR", 189 | "Europe/Lisbon": "PT", 190 | "Europe/Moscow": "RU", 191 | "Europe/Kiev": "UA", 192 | 193 | // Asia 194 | "Asia/Tokyo": "JP", 195 | "Asia/Seoul": "KR", 196 | "Asia/Shanghai": "CN", 197 | "Asia/Hong_Kong": "HK", 198 | "Asia/Taipei": "TW", 199 | "Asia/Singapore": "SG", 200 | "Asia/Bangkok": "TH", 201 | "Asia/Jakarta": "ID", 202 | "Asia/Manila": "PH", 203 | "Asia/Kuala_Lumpur": "MY", 204 | "Asia/Ho_Chi_Minh": "VN", 205 | "Asia/Kolkata": "IN", 206 | "Asia/Karachi": "PK", 207 | "Asia/Dhaka": "BD", 208 | "Asia/Colombo": "LK", 209 | "Asia/Dubai": "AE", 210 | "Asia/Riyadh": "SA", 211 | "Asia/Tehran": "IR", 212 | "Asia/Baghdad": "IQ", 213 | "Asia/Jerusalem": "IL", 214 | 215 | // Oceania 216 | "Australia/Sydney": "AU", 217 | "Australia/Melbourne": "AU", 218 | "Australia/Perth": "AU", 219 | "Pacific/Auckland": "NZ", 220 | "Pacific/Fiji": "FJ", 221 | 222 | // Africa 223 | "Africa/Cairo": "EG", 224 | "Africa/Lagos": "NG", 225 | "Africa/Johannesburg": "ZA", 226 | "Africa/Casablanca": "MA", 227 | "Africa/Algiers": "DZ", 228 | "Africa/Tunis": "TN", 229 | "Africa/Nairobi": "KE", 230 | }; 231 | 232 | if (timezoneCountryMap[timezone]) { 233 | return timezoneCountryMap[timezone]; 234 | } 235 | } catch (error) { 236 | console.log("Erro ao detectar país via timezone:", error); 237 | } 238 | 239 | try { 240 | // Método 2: Usar o locale do navegador 241 | const locale = Intl.DateTimeFormat().resolvedOptions().locale; 242 | const localeCountryMap = { 243 | "pt-BR": "BR", 244 | "en-US": "US", 245 | "en-GB": "GB", 246 | "en-CA": "CA", 247 | "en-AU": "AU", 248 | "fr-FR": "FR", 249 | "fr-CA": "CA", 250 | "de-DE": "DE", 251 | "de-AT": "AT", 252 | "de-CH": "CH", 253 | "es-ES": "ES", 254 | "es-MX": "MX", 255 | "es-AR": "AR", 256 | "es-CL": "CL", 257 | "es-CO": "CO", 258 | "es-PE": "PE", 259 | "it-IT": "IT", 260 | "it-CH": "CH", 261 | "ja-JP": "JP", 262 | "ko-KR": "KR", 263 | "zh-CN": "CN", 264 | "zh-TW": "TW", 265 | "zh-HK": "HK", 266 | "ru-RU": "RU", 267 | "pl-PL": "PL", 268 | "nl-NL": "NL", 269 | "sv-SE": "SE", 270 | "no-NO": "NO", 271 | "da-DK": "DK", 272 | "fi-FI": "FI", 273 | "th-TH": "TH", 274 | "vi-VN": "VN", 275 | "id-ID": "ID", 276 | "ms-MY": "MY", 277 | "hi-IN": "IN", 278 | "ar-SA": "SA", 279 | "ar-EG": "EG", 280 | "he-IL": "IL", 281 | "tr-TR": "TR", 282 | "uk-UA": "UA", 283 | "cs-CZ": "CZ", 284 | "hu-HU": "HU", 285 | "ro-RO": "RO", 286 | "bg-BG": "BG", 287 | "el-GR": "GR", 288 | "hr-HR": "HR", 289 | "sk-SK": "SK", 290 | "sl-SI": "SI", 291 | "et-EE": "EE", 292 | "lv-LV": "LV", 293 | "lt-LT": "LT", 294 | }; 295 | 296 | if (localeCountryMap[locale]) { 297 | return localeCountryMap[locale]; 298 | } 299 | } catch (error) { 300 | console.log("Erro ao detectar país via locale:", error); 301 | } 302 | 303 | try { 304 | // Método 3: Usar navigator.language como fallback 305 | const language = navigator.language || navigator.userLanguage; 306 | if (language.includes("-")) { 307 | const countryCode = language.split("-")[1].toUpperCase(); 308 | // Verificar se o código de país existe no nosso mapeamento 309 | if (COUNTRY_CURRENCY_MAP[countryCode]) { 310 | return countryCode; 311 | } 312 | } 313 | 314 | // Mapeamento básico por idioma 315 | const languageCountryMap = { 316 | pt: "BR", 317 | en: "US", 318 | fr: "FR", 319 | de: "DE", 320 | es: "ES", 321 | it: "IT", 322 | ja: "JP", 323 | ko: "KR", 324 | zh: "CN", 325 | ru: "RU", 326 | ar: "SA", 327 | hi: "IN", 328 | th: "TH", 329 | vi: "VN", 330 | id: "ID", 331 | ms: "MY", 332 | tr: "TR", 333 | pl: "PL", 334 | nl: "NL", 335 | sv: "SE", 336 | no: "NO", 337 | da: "DK", 338 | fi: "FI", 339 | }; 340 | 341 | const languageCode = language.split("-")[0].toLowerCase(); 342 | if (languageCountryMap[languageCode]) { 343 | return languageCountryMap[languageCode]; 344 | } 345 | } catch (error) { 346 | console.log("Erro ao detectar país via language:", error); 347 | } 348 | 349 | // Fallback final: US como padrão 350 | return "US"; 351 | } 352 | 353 | // Função para abrir doação do PayPal 354 | function openDonation() { 355 | const countryCode = detectUserCountry(); 356 | const currencyCode = COUNTRY_CURRENCY_MAP[countryCode] || "USD"; 357 | 358 | const donationUrl = `https://www.paypal.com/donate/?cmd=_donations&business=S34UMJ23659VY¤cy_code=${currencyCode}`; 359 | 360 | chrome.tabs.create({ url: donationUrl }); 361 | } 362 | 363 | document.addEventListener("DOMContentLoaded", function () { 364 | document.getElementById("startButton").addEventListener("click", function () { 365 | chrome.runtime.sendMessage({ action: "removeLikedVideos" }); 366 | }); 367 | 368 | // Adicionar event listener para o botão de doação 369 | document 370 | .getElementById("donateButton") 371 | .addEventListener("click", function () { 372 | openDonation(); 373 | }); 374 | }); 375 | --------------------------------------------------------------------------------