├── demo.png ├── icon.png ├── background.js ├── LICENSE ├── manifest.json ├── _locales ├── en │ └── messages.json ├── id │ └── messages.json ├── ms │ └── messages.json ├── tr │ └── messages.json ├── pt_BR │ └── messages.json ├── fr │ └── messages.json └── de │ └── messages.json ├── PRIVACY.md ├── README.md ├── popup.html ├── script.js ├── styles.css └── popup.js /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabireze/tiktok-all-reposted-videos-remover/HEAD/demo.png -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabireze/tiktok-all-reposted-videos-remover/HEAD/icon.png -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | chrome.runtime.onMessage.addListener(async function ( 2 | request, 3 | sender, 4 | sendResponse 5 | ) { 6 | if (request.action === "removeRepostedVideos") { 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) 2024 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 Reposted Videos Remover", 3 | "description": "A Chrome extension to remove all reposted videos on TikTok.", 4 | "version": "1.2.1", 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://chromewebstore.google.com/detail/tiktok-all-reposted-videos-remover/amgpfdpibiacligkkkbeonfhmonkgjhg", 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 Reposted 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 | "default_locale": "en" 38 | } 39 | -------------------------------------------------------------------------------- /_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appTitle": { 3 | "message": "TikTok All Reposted Videos Remover" 4 | }, 5 | "step1": { 6 | "message": "Step 1: Open tiktok.com in a new tab and make sure you're logged in to your account." 7 | }, 8 | "step2": { 9 | "message": "Step 2: Come back here and click the button below to start the process." 10 | }, 11 | "startButton": { 12 | "message": "Start Removing Reposts" 13 | }, 14 | "list1": { 15 | "message": "The extension will open your TikTok profile" 16 | }, 17 | "list2": { 18 | "message": "Enter the \"Reposts\" tab" 19 | }, 20 | "list3": { 21 | "message": "Open each reposted video" 22 | }, 23 | "list4": { 24 | "message": "Remove it from your reposts and go to the next" 25 | }, 26 | "list5": { 27 | "message": "Repeat until all reposted videos are gone" 28 | }, 29 | "note": { 30 | "message": "Note: If you have a lot of reposted videos, the process may take a while. TikTok might temporarily block actions if too many happen too fast. In that case, wait about 1 hour and try again." 31 | }, 32 | "tip": { 33 | "message": "Tip: When the process finishes, refresh your TikTok profile to confirm that all reposted videos were removed." 34 | }, 35 | "linkLiked": { 36 | "message": "Remove All Liked Videos" 37 | }, 38 | "linkFavorite": { 39 | "message": "Remove All Favorite Videos" 40 | }, 41 | "linkGitHub": { 42 | "message": "GitHub Repository" 43 | }, 44 | "donateTooltip": { 45 | "message": "Support the project" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /_locales/id/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appTitle": { 3 | "message": "TikTok All Reposted Videos Remover" 4 | }, 5 | "startButton": { 6 | "message": "Mulai Menghapus Repost" 7 | }, 8 | "step1": { 9 | "message": "Langkah 1: Buka tiktok.com di tab baru dan pastikan Anda sudah login ke akun Anda." 10 | }, 11 | "step2": { 12 | "message": "Langkah 2: Kembali ke sini dan klik tombol di bawah untuk memulai proses." 13 | }, 14 | "list1": { 15 | "message": "Ekstensi akan membuka profil TikTok Anda" 16 | }, 17 | "list2": { 18 | "message": "Masuk ke tab \"Reposts\"" 19 | }, 20 | "list3": { 21 | "message": "Buka setiap video yang di-repost" 22 | }, 23 | "list4": { 24 | "message": "Hapus dari repost dan lanjut ke berikutnya" 25 | }, 26 | "list5": { 27 | "message": "Ulangi sampai semua video repost dihapus" 28 | }, 29 | "note": { 30 | "message": "Catatan: Jika Anda memiliki banyak video repost, prosesnya bisa memakan waktu. TikTok mungkin memblokir sementara jika terlalu banyak aksi dalam waktu singkat. Tunggu sekitar 1 jam dan coba lagi." 31 | }, 32 | "tip": { 33 | "message": "Tips: Setelah proses selesai, segarkan profil TikTok Anda untuk memastikan semua video repost telah dihapus." 34 | }, 35 | "linkLiked": { 36 | "message": "Hapus Semua Video yang Disukai" 37 | }, 38 | "linkFavorite": { 39 | "message": "Hapus Semua Video Favorit" 40 | }, 41 | "linkGitHub": { 42 | "message": "Repositori GitHub" 43 | }, 44 | "donateTooltip": { 45 | "message": "Dukung proyek ini" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /_locales/ms/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appTitle": { 3 | "message": "TikTok All Reposted Videos Remover" 4 | }, 5 | "startButton": { 6 | "message": "Mulakan penghapusan repost" 7 | }, 8 | "step1": { 9 | "message": "Langkah 1: Buka tiktok.com dalam tab baharu dan pastikan anda sudah log masuk ke akaun anda." 10 | }, 11 | "step2": { 12 | "message": "Langkah 2: Kembali ke sini dan klik butang di bawah untuk memulakan." 13 | }, 14 | "list1": { 15 | "message": "Sambungan ini akan membuka profil TikTok anda" 16 | }, 17 | "list2": { 18 | "message": "Pergi ke tab \"Repost\"" 19 | }, 20 | "list3": { 21 | "message": "Buka setiap video yang telah di-repost" 22 | }, 23 | "list4": { 24 | "message": "Alih keluar video daripada repost dan teruskan ke yang seterusnya" 25 | }, 26 | "list5": { 27 | "message": "Ulangi sehingga semua repost dipadamkan" 28 | }, 29 | "note": { 30 | "message": "Nota: Jika anda mempunyai banyak video repost, proses ini mungkin mengambil masa. TikTok mungkin menyekat tindakan buat sementara waktu jika terlalu banyak dilakukan. Tunggu kira-kira 1 jam dan cuba semula." 31 | }, 32 | "tip": { 33 | "message": "Petua: Setelah proses selesai, segarkan profil TikTok anda untuk memastikan semua repost telah dipadamkan." 34 | }, 35 | "linkLiked": { 36 | "message": "Padam Semua Video Disukai" 37 | }, 38 | "linkFavorite": { 39 | "message": "Padam Semua Video Kegemaran" 40 | }, 41 | "linkGitHub": { 42 | "message": "Repositori GitHub" 43 | }, 44 | "donateTooltip": { 45 | "message": "Sokong projek ini" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /_locales/tr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appTitle": { 3 | "message": "TikTok All Reposted Videos Remover" 4 | }, 5 | "startButton": { 6 | "message": "Yeniden paylaşımları kaldırmaya başla" 7 | }, 8 | "step1": { 9 | "message": "Adım 1: Yeni bir sekmede tiktok.com'u açın ve hesabınıza giriş yaptığınızdan emin olun." 10 | }, 11 | "step2": { 12 | "message": "Adım 2: Buraya geri dönün ve başlamak için aşağıdaki butona tıklayın." 13 | }, 14 | "list1": { 15 | "message": "Eklenti TikTok profilinizi açacak" 16 | }, 17 | "list2": { 18 | "message": "\"Yeniden Paylaşımlar\" sekmesine gidin" 19 | }, 20 | "list3": { 21 | "message": "Her yeniden paylaşılan videoyu açın" 22 | }, 23 | "list4": { 24 | "message": "Videoyu yeniden paylaşımlardan kaldırın ve bir sonrakine geçin" 25 | }, 26 | "list5": { 27 | "message": "Tüm yeniden paylaşımlar silinene kadar tekrarlayın" 28 | }, 29 | "note": { 30 | "message": "Not: Eğer çok sayıda yeniden paylaşılan videonuz varsa, işlem uzun sürebilir. TikTok çok fazla işlem yapılırsa geçici olarak engelleme uygulayabilir. Yaklaşık 1 saat bekleyip tekrar deneyin." 31 | }, 32 | "tip": { 33 | "message": "İpucu: İşlem tamamlandığında, tüm yeniden paylaşımların silindiğinden emin olmak için TikTok profilinizi yenileyin." 34 | }, 35 | "linkLiked": { 36 | "message": "Tüm Beğenilen Videoları Kaldır" 37 | }, 38 | "linkFavorite": { 39 | "message": "Tüm Favori Videoları Kaldır" 40 | }, 41 | "linkGitHub": { 42 | "message": "GitHub Deposu" 43 | }, 44 | "donateTooltip": { 45 | "message": "Projeyi destekle" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /_locales/pt_BR/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appTitle": { 3 | "message": "TikTok All Reposted Videos Remover" 4 | }, 5 | "step1": { 6 | "message": "Passo 1: Abra tiktok.com em uma nova aba e certifique-se de estar logado em sua conta." 7 | }, 8 | "step2": { 9 | "message": "Passo 2: Volte aqui e clique no botão abaixo para iniciar o processo." 10 | }, 11 | "startButton": { 12 | "message": "Iniciar Remoção de Reposts" 13 | }, 14 | "list1": { 15 | "message": "A extensão abrirá seu perfil do TikTok" 16 | }, 17 | "list2": { 18 | "message": "Entrará na aba de \"Reposts\"" 19 | }, 20 | "list3": { 21 | "message": "Abrirá cada vídeo repostado" 22 | }, 23 | "list4": { 24 | "message": "Removerá dos reposts e passará para o próximo" 25 | }, 26 | "list5": { 27 | "message": "Repetirá até que todos os reposts sejam removidos" 28 | }, 29 | "note": { 30 | "message": "Observação: Se você tiver muitos vídeos repostados, o processo pode demorar. O TikTok pode bloquear ações temporariamente se muitas acontecerem em pouco tempo. Nesse caso, aguarde cerca de 1 hora e tente novamente." 31 | }, 32 | "tip": { 33 | "message": "Dica: Quando o processo terminar, atualize seu perfil do TikTok para confirmar que todos os vídeos repostados foram removidos." 34 | }, 35 | "linkLiked": { 36 | "message": "Remover Todos os Vídeos Curtidos" 37 | }, 38 | "linkFavorite": { 39 | "message": "Remover Todos os Vídeos Favoritos" 40 | }, 41 | "linkGitHub": { 42 | "message": "Repositório no GitHub" 43 | }, 44 | "donateTooltip": { 45 | "message": "Apoie o projeto" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /_locales/fr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appTitle": { 3 | "message": "TikTok All Reposted Videos Remover" 4 | }, 5 | "startButton": { 6 | "message": "Commencer la suppression des reposts" 7 | }, 8 | "step1": { 9 | "message": "Étape 1 : Ouvrez tiktok.com dans un nouvel onglet et assurez-vous d'être connecté à votre compte." 10 | }, 11 | "step2": { 12 | "message": "Étape 2 : Revenez ici et cliquez sur le bouton ci-dessous pour commencer." 13 | }, 14 | "list1": { 15 | "message": "L'extension ouvrira votre profil TikTok" 16 | }, 17 | "list2": { 18 | "message": "Accéder à l'onglet \"Reposts\"" 19 | }, 20 | "list3": { 21 | "message": "Ouvrir chaque vidéo repostée" 22 | }, 23 | "list4": { 24 | "message": "Supprimer la vidéo des reposts et passer à la suivante" 25 | }, 26 | "list5": { 27 | "message": "Répéter jusqu'à suppression de tous les reposts" 28 | }, 29 | "note": { 30 | "message": "Remarque : Si vous avez beaucoup de vidéos repostées, le processus peut être long. TikTok peut temporairement bloquer les actions si elles sont trop nombreuses. Attendez environ 1 heure et réessayez." 31 | }, 32 | "tip": { 33 | "message": "Conseil : Une fois le processus terminé, actualisez votre profil TikTok pour vérifier que tous les reposts ont été supprimés." 34 | }, 35 | "linkLiked": { 36 | "message": "Supprimer toutes les vidéos aimées" 37 | }, 38 | "linkFavorite": { 39 | "message": "Supprimer toutes les vidéos favorites" 40 | }, 41 | "linkGitHub": { 42 | "message": "Dépôt GitHub" 43 | }, 44 | "donateTooltip": { 45 | "message": "Soutenir le projet" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /_locales/de/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appTitle": { 3 | "message": "TikTok All Reposted Videos Remover" 4 | }, 5 | "startButton": { 6 | "message": "Reposts entfernen starten" 7 | }, 8 | "step1": { 9 | "message": "Schritt 1: Öffne tiktok.com in einem neuen Tab und stelle sicher, dass du in deinem Konto eingeloggt bist." 10 | }, 11 | "step2": { 12 | "message": "Schritt 2: Kehre hierher zurück und klicke auf die Schaltfläche unten, um zu beginnen." 13 | }, 14 | "list1": { 15 | "message": "Die Erweiterung öffnet dein TikTok-Profil" 16 | }, 17 | "list2": { 18 | "message": "Gehe zum Tab „Reposts“" 19 | }, 20 | "list3": { 21 | "message": "Öffne jedes erneut gepostete Video" 22 | }, 23 | "list4": { 24 | "message": "Entferne das Video aus den Reposts und gehe zum nächsten" 25 | }, 26 | "list5": { 27 | "message": "Wiederhole dies, bis alle Reposts entfernt wurden" 28 | }, 29 | "note": { 30 | "message": "Hinweis: Wenn du viele erneut gepostete Videos hast, kann der Vorgang etwas dauern. TikTok kann temporär Aktionen blockieren, wenn zu viele durchgeführt werden. Warte etwa eine Stunde und versuche es erneut." 31 | }, 32 | "tip": { 33 | "message": "Tipp: Wenn der Vorgang abgeschlossen ist, aktualisiere dein TikTok-Profil, um zu überprüfen, ob alle Reposts entfernt wurden." 34 | }, 35 | "linkLiked": { 36 | "message": "Alle „Gefällt mir“-Videos entfernen" 37 | }, 38 | "linkFavorite": { 39 | "message": "Alle Favoriten-Videos entfernen" 40 | }, 41 | "linkGitHub": { 42 | "message": "GitHub-Repository" 43 | }, 44 | "donateTooltip": { 45 | "message": "Projekt unterstützen" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /PRIVACY.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | ## Overview 4 | 5 | TikTok All Reposted Videos Remover is a Chrome extension designed to help users automatically remove all reposted videos from their TikTok profile. We are committed to protecting your privacy and ensuring that no personal data is ever collected, stored, or shared. 6 | 7 | --- 8 | 9 | ## What Data We Collect 10 | 11 | **We do not collect or transmit any personal data.** 12 | All extension logic runs entirely within your browser, and no data is sent to any external server. 13 | 14 | Specifically: 15 | - We do **not** collect your TikTok credentials. 16 | - We do **not** access your TikTok content beyond what is necessary to automate the repost removal. 17 | - We do **not** track your activity or browsing behavior. 18 | 19 | --- 20 | 21 | ## How the Extension Works 22 | 23 | The extension performs the following actions: 24 | 25 | - Opens [tiktok.com](https://www.tiktok.com) in a new browser tab. 26 | - Navigates to your TikTok profile. 27 | - Accesses the “Reposts” tab. 28 | - Opens each reposted video and simulates a click to remove the repost. 29 | - Repeats the process until all reposted videos are removed. 30 | 31 | All actions are executed **locally in your browser** and require no server communication. 32 | 33 | --- 34 | 35 | ## Third-Party Services 36 | 37 | This extension does **not** use any third-party analytics, tracking scripts, or external APIs. 38 | 39 | --- 40 | 41 | ## Permissions Explanation 42 | 43 | The extension uses the following Chrome permissions: 44 | 45 | - **`scripting`**: Required to execute the removal logic on the TikTok page. 46 | - **`host_permissions`** (`https://www.tiktok.com/*`): Grants access exclusively to TikTok pages for automation. No other sites are accessed. 47 | 48 | These permissions are strictly necessary for the extension to perform its intended function. 49 | 50 | --- 51 | 52 | ## Contact 53 | 54 | If you have any questions or concerns regarding privacy, feel free to reach out: 55 | 56 | **Developer:** Gabriel de Rezende Gonçalves 57 | **Website:** [gabireze.com.br](https://gabireze.com.br) 58 | **GitHub:** [github.com/gabireze](https://github.com/gabireze) 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TikTok All Reposted Videos Remover 2 | 3 | Remove all your reposted videos on TikTok automatically — no clicks, no scrolling, just one button. 4 | 5 | ![Screenshot](demo.png) 6 | 7 | --- 8 | 9 | ## GitAds Sponsored 10 | [![Sponsored by GitAds](https://gitads.dev/v1/ad-serve?source=gabireze/tiktok-all-reposted-videos-remover@github)](https://gitads.dev/v1/ad-track?source=gabireze/tiktok-all-reposted-videos-remover@github) 11 | 12 | --- 13 | 14 | ## Features 15 | 16 | - ✅ Opens your TikTok profile in a new tab automatically 17 | - ✅ Accesses your "Reposts" tab 18 | - ✅ Enters each reposted video and removes it from your reposts 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://chromewebstore.google.com/detail/tiktok-all-reposted-video/amgpfdpibiacligkkkbeonfhmonkgjhg) 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 Reposts"**. 44 | 4. A new TikTok tab will open automatically. 45 | 5. ✅ The extension will: 46 | - Navigate to your profile 47 | - Open the "Reposts" tab 48 | - Remove each reposted 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 reposted 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 Reposts 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 Reposted Videos Remover 6 | 7 | 11 | 15 | 16 | 17 | 18 |
19 | 29 | 30 |
31 | 39 | 67 |
68 |
69 | 70 |
71 |

72 | 73 |

74 | 75 |

76 | 77 |

78 | 79 | 82 | 83 | 90 | 91 |

92 | 93 |

94 | 95 |

96 | 97 |

98 |
99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | const initiateRepostsVideosRemoval = 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 clickRepostTab = async () => { 36 | try { 37 | const repostTab = await waitForElement('[class*="PRepost"]'); 38 | repostTab.click(); 39 | console.log("Successfully opened the 'Reposts' tab."); 40 | await sleep(5000); 41 | } catch (error) { 42 | stopScript("Error clicking the 'Reposts' tab", error); 43 | } 44 | }; 45 | 46 | const clickRepostVideo = async () => { 47 | try { 48 | const firstVideo = await waitForElement('[class*="DivPlayerContainer"]'); 49 | firstVideo.click(); 50 | console.log("Successfully opened the first reposted video."); 51 | await sleep(5000); 52 | } catch (error) { 53 | stopScript("No reposted videos found or unable to open", error); 54 | } 55 | }; 56 | 57 | const clickNextRepostAndRemove = async () => { 58 | try { 59 | const interval = setInterval(async () => { 60 | const nextVideoButton = document.querySelector( 61 | '[data-e2e="arrow-right"]' 62 | ); 63 | const repostButton = document.querySelector( 64 | '[data-e2e="video-share-repost"]' 65 | ); 66 | 67 | if (!repostButton) { 68 | clearInterval(interval); 69 | stopScript("Repost button not found"); 70 | return; 71 | } 72 | 73 | repostButton.click(); 74 | console.log("Removed repost 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 reposted video."); 84 | }, 2000); 85 | } catch (error) { 86 | stopScript("Error during reposted 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 clickRepostTab(); 122 | await clickRepostVideo(); 123 | await clickNextRepostAndRemove(); 124 | } catch (error) { 125 | stopScript("Unexpected error in main flow", error); 126 | } 127 | }; 128 | 129 | initiateRepostsVideosRemoval(); 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 | // Processa elementos com data-i18n para innerHTML 365 | document.querySelectorAll("[data-i18n]").forEach((element) => { 366 | const key = element.getAttribute("data-i18n"); 367 | const message = chrome.i18n.getMessage(key); 368 | if (message) element.innerHTML = message; 369 | }); 370 | 371 | // Processa elementos com data-i18n-title para atributo title 372 | document.querySelectorAll("[data-i18n-title]").forEach((element) => { 373 | const key = element.getAttribute("data-i18n-title"); 374 | const message = chrome.i18n.getMessage(key); 375 | if (message) element.title = message; 376 | }); 377 | 378 | const startButton = document.getElementById("startButton"); 379 | if (startButton) { 380 | startButton.addEventListener("click", function () { 381 | chrome.runtime.sendMessage({ action: "removeRepostedVideos" }); 382 | }); 383 | } 384 | 385 | // Adicionar event listener para o botão de doação 386 | const donateButton = document.getElementById("donateButton"); 387 | if (donateButton) { 388 | donateButton.addEventListener("click", function () { 389 | openDonation(); 390 | }); 391 | } 392 | }); 393 | --------------------------------------------------------------------------------