├── 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-favorite-videos-remover/HEAD/icon.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabireze/tiktok-all-favorite-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 === "removeFavoriteVideos") { 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 Favorite Videos Remover", 3 | "description": "A Chrome extension to remove all favorite 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-favorite-video/cbjkccccmffolddklbkedlndlfokcpbn", 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 Favorite Video 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 Favorite Videos Remover is a Chrome extension designed to help users remove all their favorite videos on TikTok with a single click. 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 runs entirely in your browser and does not send any information to external servers. 13 | 14 | Specifically: 15 | - We do **not** collect your TikTok credentials. 16 | - We do **not** access your TikTok content beyond what is needed to automate the removal of favorites. 17 | - We do **not** track your browsing activity or behavior. 18 | 19 | --- 20 | 21 | ## How the Extension Works 22 | 23 | The extension performs the following actions: 24 | - Opens [tiktok.com](https://www.tiktok.com) in a new tab. 25 | - Automates navigation to your profile and Favorites tab. 26 | - Simulates user actions to remove favorite videos from your account. 27 | 28 | All actions happen **locally in your browser**. No data is stored or transmitted externally. 29 | 30 | --- 31 | 32 | ## Third-Party Services 33 | 34 | This extension does **not** use any third-party analytics, tracking scripts, or external APIs. 35 | 36 | --- 37 | 38 | ## Permissions Explanation 39 | 40 | The extension uses the following Chrome permissions: 41 | 42 | - **`tabs`**: Used to open a TikTok tab and inject automation scripts. 43 | - **`scripting`**: Required to execute the removal logic on the TikTok page. 44 | - **`host_permissions`** (`https://www.tiktok.com/*`): Grants access only to TikTok for script execution. No other websites are accessed. 45 | 46 | These permissions are strictly necessary to fulfill the core functionality of the extension. 47 | 48 | --- 49 | 50 | ## Contact 51 | 52 | If you have any questions or privacy concerns, feel free to reach out: 53 | 54 | **Developer:** Gabriel de Rezende Gonçalves 55 | **GitHub:** [gabireze](https://github.com/gabireze) 56 | 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TikTok All Favorite Videos Remover 2 | 3 | Remove all your favorite 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-favorite-videos-remover@github)](https://gitads.dev/v1/ad-track?source=gabireze/tiktok-all-favorite-videos-remover@github) 11 | 12 | --- 13 | 14 | ## Features 15 | 16 | - ✅ Opens your TikTok profile in a new tab automatically 17 | - ✅ Accesses your "Favorites" tab 18 | - ✅ Enters each video and removes it from favorites 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-favorite-video/cbjkccccmffolddklbkedlndlfokcpbn) 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 Favorites"**. 44 | 4. A new TikTok tab will open automatically. 45 | 5. ✅ The extension will: 46 | - Navigate to your profile 47 | - Open the "Favorites" tab 48 | - Remove each video from your favorites 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 favorites 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 Favorites 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 Favorite Video Remover 6 | 7 | 11 | 15 | 16 | 17 | 18 |
19 | 27 |
28 | 35 | 60 |
61 |
62 | 63 |
64 |

65 | Before you start: Make sure you are 66 | logged in to your account at 67 | tiktok.com. 68 |

69 | 70 |

71 | Step: Click the button below. A new TikTok tab will 72 | open automatically and the extension will begin the removal process. 73 |

74 | 75 | 76 | 77 | 84 | 85 |

86 | Note: If you have many favorite videos, the process may 87 | take a while. TikTok may temporarily block actions if too many happen 88 | quickly. If that occurs, wait about 1 hour and try 89 | again. 90 |

91 | 92 |

93 | Tip: When the process finishes, refresh your TikTok 94 | favorites tab to verify the videos were removed. 95 |

96 |
97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | const initiateFavoriteVideosRemoval = 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 clickFavoriteTab = async () => { 36 | try { 37 | const favoriteTab = await waitForElement('[class*="PFavorite"]'); 38 | favoriteTab.click(); 39 | console.log("Successfully opened the 'Favorites' tab."); 40 | await sleep(5000); 41 | } catch (error) { 42 | stopScript("Error clicking the 'Favorites' tab", error); 43 | } 44 | }; 45 | 46 | const clickFavoriteVideo = async () => { 47 | try { 48 | const firstVideo = await waitForElement('[class*="DivPlayerContainer"]'); 49 | firstVideo.click(); 50 | console.log("Successfully opened the first favorite video."); 51 | await sleep(5000); 52 | } catch (error) { 53 | stopScript("No favorite videos found or unable to open", error); 54 | } 55 | }; 56 | 57 | const clickNextFavoriteAndRemove = async () => { 58 | try { 59 | const interval = setInterval(async () => { 60 | const nextVideoButton = document.querySelector( 61 | '[data-e2e="arrow-right"]' 62 | ); 63 | const favoriteButton = document.querySelector( 64 | '[data-e2e="undefined-icon"]' 65 | ); 66 | 67 | if (!favoriteButton) { 68 | clearInterval(interval); 69 | stopScript("Favorite button not found"); 70 | return; 71 | } 72 | 73 | favoriteButton.click(); 74 | console.log("Removed favorite 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 favorite video."); 84 | }, 2000); 85 | } catch (error) { 86 | stopScript("Error during favorite 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 clickFavoriteTab(); 122 | await clickFavoriteVideo(); 123 | await clickNextFavoriteAndRemove(); 124 | } catch (error) { 125 | stopScript("Unexpected error in main flow", error); 126 | } 127 | }; 128 | 129 | initiateFavoriteVideosRemoval(); 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 | } -------------------------------------------------------------------------------- /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: "removeFavoriteVideos" }); 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 | }); --------------------------------------------------------------------------------