diff --git a/README.md b/README.md index 947a8c3..ca07778 100644 --- a/README.md +++ b/README.md @@ -1,337 +1,217 @@ -# UserScript Project Template +# TikTok Quick Block -[![GitHub license](https://img.shields.io/github/license/JosunLP/UserScriptProjectTemplate?style=for-the-badge)](https://github.com/JosunLP/UserScriptProjectTemplate/blob/main/LICENSE) -[![GitHub issues](https://img.shields.io/github/issues/JosunLP/UserScriptProjectTemplate?style=for-the-badge)](https://github.com/JosunLP/UserScriptProjectTemplate/issues) -[![GitHub stars](https://img.shields.io/github/stars/JosunLP/UserScriptProjectTemplate?style=for-the-badge)](https://github.com/JosunLP/UserScriptProjectTemplate/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/JosunLP/UserScriptProjectTemplate?style=for-the-badge)](https://github.com/JosunLP/UserScriptProjectTemplate/network) - -[![TypeScript](https://img.shields.io/badge/TypeScript-5.4+-blue?logo=typescript&style=for-the-badge)](https://www.typescriptlang.org/) -[![Vite](https://img.shields.io/badge/Vite-7.0+-646CFF?logo=vite&style=for-the-badge)](https://vitejs.dev/) -[![ESLint](https://img.shields.io/badge/ESLint-8.57+-4B32C3?logo=eslint&style=for-the-badge)](https://eslint.org/) -[![Prettier](https://img.shields.io/badge/Prettier-3.6+-F7B93E?logo=prettier&style=for-the-badge)](https://prettier.io/) -[![Node.js](https://img.shields.io/badge/Node.js-18+-339933?logo=node.js&style=for-the-badge)](https://nodejs.org/) - -[![Tampermonkey](https://img.shields.io/badge/Tampermonkey-Compatible-00485B?logo=tampermonkey&style=for-the-badge)](https://www.tampermonkey.net/) -[![Greasemonkey](https://img.shields.io/badge/Greasemonkey-Compatible-FF6600?logo=firefox&style=for-the-badge)](https://www.greasespot.net/) -[![Violentmonkey](https://img.shields.io/badge/Violentmonkey-Compatible-663399?logo=violentmonkey&style=for-the-badge)](https://violentmonkey.github.io/) -[![Mobile Support](https://img.shields.io/badge/Mobile-Support-00C851?logo=android&style=for-the-badge)](https://github.com/JosunLP/UserScriptProjectTemplate#mobile-browser-support) - -[![Last Commit](https://img.shields.io/github/last-commit/JosunLP/UserScriptProjectTemplate?style=for-the-badge)](https://github.com/JosunLP/UserScriptProjectTemplate/commits) -[![Contributors](https://img.shields.io/github/contributors/JosunLP/UserScriptProjectTemplate?style=for-the-badge)](https://github.com/JosunLP/UserScriptProjectTemplate/graphs/contributors) -[![Repository Size](https://img.shields.io/github/repo-size/JosunLP/UserScriptProjectTemplate?style=for-the-badge)](https://github.com/JosunLP/UserScriptProjectTemplate) - -## Description - -A modern, production-ready template for building UserScripts using TypeScript and Vite. This template provides a solid foundation with best practices, type safety, and modern development tools for creating sophisticated Tampermonkey and Greasemonkey scripts. +A UserScript that adds a "Quick Block" button directly to TikTok comments, allowing you to block comment authors with a single click. ## Features -- 🚀 **Modern Tech Stack:** TypeScript, Vite, ESLint, Prettier -- 🛡️ **Type Safety:** Strict TypeScript configuration with comprehensive UserScript API definitions -- 🔧 **Development Tools:** ESLint, Prettier, automated build pipeline -- 🎯 **Environment Support:** Separate development and production configurations -- 📦 **Modular Architecture:** Component system with reusable utilities -- 💾 **Storage Management:** Type-safe wrapper for GM_setValue/GM_getValue -- 🛠️ **Build System:** Optimized Vite configuration with automatic header generation -- 🎨 **DOM Utilities:** Helper functions for element manipulation and waiting -- 🔒 **Error Handling:** Comprehensive error boundary system -- ⚡ **Event System:** Type-safe event emitter for module communication -- 📱 **Mobile Support:** Touch-optimized interface with mobile browser detection -- 🤏 **Touch Gestures:** Built-in touch event handling and gesture recognition -- 📲 **Responsive Design:** Mobile-first CSS with safe area support for notched devices +- ⚡ **One-Click Blocking**: Add a quick block button to every comment on TikTok +- 🚫 **Instant Results**: Comments from blocked users are immediately hidden and blurred +- 💾 **Persistent Storage**: Blocked users are saved across browser sessions +- 📱 **Mobile Optimized**: Works on mobile browsers with touch support +- 🎨 **Beautiful Design**: Styled buttons that match TikTok's design language +- 📊 **Statistics**: Track how many users you've blocked and buttons injected +- 🔔 **Notifications**: Get visual feedback when blocking users ## Installation -### Quick Start - -```bash -git clone https://github.com/JosunLP/UserScriptProjectTemplate.git -cd UserScriptProjectTemplate -npm install -``` +### Prerequisites -### Development Setup +You need a UserScript manager installed in your browser: -```bash -# Install dependencies -npm install +**Desktop Browsers:** +- [Tampermonkey](https://www.tampermonkey.net/) (Chrome, Firefox, Safari, Edge, Opera) +- [Greasemonkey](https://www.greasespot.net/) (Firefox) +- [Violentmonkey](https://violentmonkey.github.io/) (Chrome, Firefox, Edge) -# Start development mode with auto-rebuild -npm run dev +**Mobile Browsers:** +- [Kiwi Browser](https://kiwibrowser.com/) (Android) - Built-in Chrome extension support +- [Firefox Mobile](https://www.mozilla.org/firefox/mobile/) (Android) - Install Greasemonkey or Tampermonkey +- [Microsoft Edge Mobile](https://www.microsoft.com/edge/mobile) - Install Tampermonkey -# Type checking -npm run type-check +### Steps -# Linting and formatting -npm run validate -``` +1. Install a UserScript manager (see above) +2. Open the `dist/tiktok-quick-block.user.js` file +3. Your UserScript manager should detect it and prompt you to install +4. Confirm the installation +5. Visit [TikTok](https://www.tiktok.com/) ## Usage -### Project Structure - -```bash -src/ -├── types/ # TypeScript type definitions -├── utils/ # Utility functions (Storage, DOM, Events) -├── core/ # Core application logic -├── modules/ # Feature modules -└── index.ts # Main application entry point - -tools/ -├── userScriptHeader.ts # UserScript header generator - -assets/ # Icons and static resources -└── icon.afdesign -``` - -### Configuration - -The main configuration is in `header.config.json`. This file controls UserScript metadata generation: - -```json -{ - "environments": { - "development": { - "includes": ["http://localhost:*/*", "https://localhost:*/*"], - "grants": ["GM_setValue", "GM_getValue", "GM_log", "GM_notification"] - }, - "production": { - "includes": ["https://your-domain.com/*"], - "grants": ["GM_setValue", "GM_getValue"] - } - } -} -``` - -### Build Commands +### Blocking Users -```bash -# Development -npm run dev # Start development with watch mode -npm run dev:build # Single development build with header -npm run dev:header # Generate header for existing dev build - -# Production -npm run build # Build for production -npm run build:prod # Explicit production build - -# Quality Assurance -npm run validate # Type check + lint -npm run lint # ESLint with auto-fix -npm run format # Prettier formatting - -# Utilities -npm run clean # Clean dist folder -npm run type-check # TypeScript type checking -``` +1. Navigate to any TikTok video with comments +2. Look for the "⚡ Quick Block" button next to each comment +3. Click the button to instantly block that user +4. The comment will be hidden and blurred +5. A notification will confirm the block -### Build Optimization +### Managing Blocked Users -The template features advanced build optimization for production: +Access the UserScript menu (click the Tampermonkey icon in your browser) to: -| Build Type | File Size | Compressed | Features | -| --------------- | --------- | ---------- | -------------------------------------- | -| **Development** | ~115 KB | ~30 KB | Source maps, debug info, readable code | -| **Production** | ~25 KB | ~6 KB | Minified, tree-shaken, optimized | +- **View Blocked Users**: See a list of all blocked users +- **Clear Blocked Users**: Remove all blocks and start fresh +- **Show Statistics**: View stats about blocks and button injections -**Production optimizations include:** +### Unblocking Users -- ⚡ **Terser minification** with aggressive compression settings -- 🌳 **Tree-shaking** to remove unused code -- 🎯 **Dead code elimination** for **DEV** blocks -- 📦 **Module inlining** for single-file output -- 🔧 **Property mangling** for smaller variable names -- 🚀 **ES2020 target** for modern JavaScript features -- 💾 **GZIP compression** reducing size by ~75% +Currently, you can unblock all users by: +1. Click the UserScript manager icon +2. Select "Clear Blocked Users" +3. Confirm the action +4. Refresh the page -### Development Workflow +## Development -1. **Configure your script** in `header.config.json` -2. **Start development:** `npm run dev` -3. **Write your code** in the `src/` directory -4. **Build for production:** `npm run build` -5. **Install the UserScript** from `dist/` folder in Tampermonkey/Greasemonkey +### Build from Source -### Storage Management +```bash +# Install dependencies +npm install -The template includes a type-safe storage system: +# Development build (with watch mode) +npm run dev -```typescript -import { Storage } from '@/utils/storage'; +# Development build (single build) +npm run dev:build -// Save data -Storage.set('userData', { name: 'John', visits: 5 }); +# Production build +npm run build:prod -// Get data with default value -const userData = Storage.get('userData', { name: '', visits: 0 }); +# Type checking +npm run type-check -// Check if key exists -if (Storage.has('userData')) { - // Key exists -} +# Linting +npm run lint +npm run lint:fix -// Remove data -Storage.remove('userData'); +# Format code +npm run format ``` -### DOM Utilities - -Helper functions for DOM manipulation: - -```typescript -import { DOMUtils } from '@/utils/dom'; - -// Wait for element to appear -const element = await DOMUtils.waitForElement('.my-selector'); - -// Add custom styles -DOMUtils.addStyles(` - .my-class { color: red; } -`); +### Project Structure -// Create element with attributes -const button = DOMUtils.createElement('button', { - textContent: 'Click me', - onclick: () => console.log('Clicked!'), -}); ``` - -### Event System - -Type-safe communication between modules: - -```typescript -import { EventEmitter } from '@/utils/events'; - -interface MyEvents { - userAction: { userId: string }; - dataLoaded: { count: number }; -} - -const emitter = new EventEmitter(); - -// Listen for events -emitter.on('userAction', ({ userId }) => { - console.log(`User ${userId} performed an action`); -}); - -// Emit events -emitter.emit('userAction', { userId: '123' }); +tiktok-quick-block/ +├── src/ +│ ├── index.ts # Main entry point +│ ├── modules/ +│ │ └── tiktok-quick-block.ts # Core blocking functionality +│ └── utils/ +│ ├── dom.ts # DOM manipulation helpers +│ ├── events.ts # Event emitter +│ ├── storage.ts # Persistent storage wrapper +│ └── mobile.ts # Mobile browser detection +├── dist/ # Built userscript +├── header.config.json # UserScript metadata +├── package.json # Project dependencies +├── vite.config.ts # Build configuration +└── tsconfig.json # TypeScript configuration ``` -### Module System - -Create reusable, event-driven modules: +### How It Works -```typescript -import { EventEmitter } from '@/utils/events'; +1. **Initialization**: The script waits for TikTok's page to load +2. **Comment Detection**: Uses MutationObserver to detect comments as they appear +3. **Button Injection**: Injects a styled "Quick Block" button next to each comment +4. **User Blocking**: Stores blocked usernames in browser storage +5. **Comment Hiding**: Applies visual effects to hide blocked users' comments +6. **Continuous Monitoring**: Watches for new comments and processes them automatically -interface ModuleEvents { - initialized: void; - actionPerformed: { action: string }; -} +### Technical Details -export class MyModule extends EventEmitter { - async initialize() { - // Module initialization logic - this.emit('initialized', undefined); - } -} -``` +- Built with **TypeScript** for type safety +- Uses **Vite** for fast builds and optimizations +- Leverages **GM_setValue/GM_getValue** for persistent storage +- Mobile-first responsive design +- Supports both desktop and mobile TikTok layouts -### Mobile Utilities +## Browser Support -Mobile-specific functionality for touch-enabled devices: +### Desktop +- ✅ Chrome/Chromium +- ✅ Firefox +- ✅ Safari +- ✅ Edge +- ✅ Opera -```typescript -import { MobileUtils } from '@/utils/mobile'; +### Mobile +- ✅ Kiwi Browser (Android) +- ✅ Firefox Mobile (Android) +- ✅ Edge Mobile (Android/iOS) +- ✅ Safari Mobile (iOS) - with Userscripts app +- ⚠️ Other mobile browsers may work with appropriate UserScript managers -// Detect mobile browser and capabilities -const detection = MobileUtils.detect(); -console.log('Is Mobile:', detection.isMobile); -console.log('Has Touch:', detection.hasTouch); -console.log('Browser:', detection.browser); +## Permissions -// Add mobile-optimized styles -if (detection.isMobile) { - MobileUtils.addMobileStyles(); -} +This UserScript requires the following permissions: -// Unified touch/mouse event handling -MobileUtils.addUnifiedEventListener(element, 'start', event => { - const position = MobileUtils.getEventPosition(event); - console.log('Touch/click at:', position); -}); +- `GM_setValue` / `GM_getValue`: Store blocked users list +- `GM_deleteValue` / `GM_listValues`: Manage stored data +- `GM_notification`: Show block confirmation notifications +- `GM_registerMenuCommand`: Add menu commands for management -// Create mobile-friendly buttons -const button = mobileModule.createMobileButton('Action', () => { - console.log('Button pressed'); -}); +## Privacy -// Orientation detection -console.log('Portrait mode:', MobileUtils.isPortrait()); -``` +- All data is stored **locally** in your browser +- No external servers or tracking +- No data is sent to third parties +- Blocked users list is stored using browser's UserScript storage -## UserScript Compatibility +## Troubleshooting -- **Tampermonkey:** Full support with all GM\_\* APIs -- **Greasemonkey:** Compatible with standard UserScript APIs -- **Violentmonkey:** Full compatibility -- **Safari:** Works with userscript managers +### Buttons not appearing? -### Mobile Browser Support +- Make sure you're on a TikTok video page with comments +- Refresh the page +- Check that the UserScript is enabled in your UserScript manager +- Open browser console and look for any errors -**Android:** +### Comments still visible after blocking? -- **Kiwi Browser:** Full Chrome extension + UserScript support -- **Microsoft Edge Mobile:** Tampermonkey support -- **Firefox Mobile:** Greasemonkey, Tampermonkey, Violentmonkey -- **Yandex Browser:** Chrome extension support +- Try refreshing the page +- Clear blocked users and try blocking again +- Check browser console for errors -**iOS:** +### Mobile issues? -- **Safari Mobile:** Tampermonkey or Userscripts App -- Limited support due to iOS restrictions - -### Mobile Features - -- **Touch Gestures:** Tap, swipe, and pinch detection -- **Responsive Design:** Mobile-first CSS with viewport adaptation -- **Safe Area Support:** Automatic handling of notched devices -- **Orientation Detection:** Portrait/landscape change handling -- **Mobile-Optimized UI:** Touch-friendly buttons and menus +- Ensure your mobile browser supports UserScripts +- Try Kiwi Browser (Android) for best compatibility +- Make sure Tampermonkey is properly installed and enabled ## Contributing -1. Fork the repository -2. Create a feature branch: `git checkout -b feature/amazing-feature` -3. Make your changes and ensure tests pass: `npm run validate` -4. Commit your changes: `git commit -m 'Add amazing feature'` -5. Push to the branch: `git push origin feature/amazing-feature` -6. Open a Pull Request +This project was created from the [UserScript Project Template](https://github.com/JosunLP/UserScriptProjectTemplate). -## Development Guidelines - -- Follow TypeScript best practices -- Use meaningful variable and function names -- Add proper error handling -- Write self-documenting code -- Follow the established project structure -- Run `npm run validate` before committing +To contribute: +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Test thoroughly +5. Submit a pull request ## License -This project is licensed under the [MIT License](https://opensource.org/licenses/MIT). +MIT License - See LICENSE file for details ## Author -**_Jonas Pfalzgraf_** +Jonas Pfalzgraf + +## Acknowledgments -- Email: [info@josunlp.de](mailto:info@josunlp.de) -- GitHub: [@JosunLP](https://github.com/JosunLP) -- Website: [josunlp.de](https://josunlp.de) +Built with the [UserScript Project Template](https://github.com/JosunLP/UserScriptProjectTemplate) which provides: +- Modern TypeScript tooling +- Mobile browser support +- Build optimization +- Developer utilities + +## Disclaimer + +This UserScript is provided as-is. TikTok's structure may change, which could break functionality. The script only works on the TikTok web app, not the mobile app. --- -**_Built with ❤️ for the UserScript community_** +**Note**: This is an independent project and is not affiliated with, endorsed by, or connected to TikTok or ByteDance. + diff --git a/header.config.json b/header.config.json index 82b3eff..0a5da17 100644 --- a/header.config.json +++ b/header.config.json @@ -1,15 +1,15 @@ { "updateUrl": "", "downloadUrl": "", - "supportUrl": "", + "supportUrl": "https://github.com/JosunLP/UserScriptProjectTemplate/issues", "iconUrl": "./assets/icon.png", "environments": { "development": { "includes": [ "http://localhost:*/*", "https://localhost:*/*", - "https://*.josunlp.de/*", - "https://josunlp.de/*" + "https://*.tiktok.com/*", + "https://tiktok.com/*" ], "excludes": [], "grants": [ @@ -24,13 +24,17 @@ ] }, "production": { - "includes": ["https://*.josunlp.de/*", "https://josunlp.de/*"], + "includes": [ + "https://*.tiktok.com/*", + "https://tiktok.com/*" + ], "excludes": [], "grants": [ "GM_setValue", "GM_getValue", "GM_deleteValue", "GM_listValues", + "GM_notification", "GM_registerMenuCommand" ] } diff --git a/package-lock.json b/package-lock.json index a8ec9f4..46248c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "userscript-project-template", - "version": "0.0.1", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "userscript-project-template", - "version": "0.0.1", + "version": "1.0.0", "license": "MIT", "devDependencies": { "@typescript-eslint/eslint-plugin": "^7.18.0", diff --git a/package.json b/package.json index af697bd..387afd6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "userscript-project-template", + "name": "tiktok-quick-block", "version": "1.0.0", - "description": "A modern, production-ready template for building UserScripts using TypeScript and Vite with mobile browser support, touch gestures, and responsive design for Tampermonkey and Greasemonkey scripts.", + "description": "TikTok Quick Block - Add a quick block button directly in comments to instantly block comment authors on TikTok web app.", "main": "index.ts", "module": "node", "scripts": { @@ -27,18 +27,12 @@ "userscript", "tampermonkey", "greasemonkey", + "tiktok", + "quick-block", + "comment-blocking", + "browser-extension", "typescript", - "template", - "vite", - "browser-automation", - "web-enhancement", - "browser-scripting", - "mobile-support", - "touch-gestures", - "responsive-design", - "mobile-browser", - "kiwi-browser", - "edge-mobile" + "vite" ], "author": "Jonas Pfalzgraf ", "license": "MIT", diff --git a/src/index.ts b/src/index.ts index af7f181..bbd011b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,6 @@ -import { ExampleModule } from '@/modules/example'; -import { DOMUtils } from '@/utils/dom'; +import { TikTokQuickBlockModule } from '@/modules/tiktok-quick-block'; import { EventEmitter } from '@/utils/events'; import { MobileUtils } from '@/utils/mobile'; -import { Storage } from '@/utils/storage'; /** * Application events interface @@ -67,7 +65,7 @@ class App extends EventEmitter { * Override this method in your implementation */ protected async main(): Promise { - console.log('👋 Hello from UserScript Template!'); + console.log('🚀 TikTok Quick Block - Starting...'); // Mobile detection and setup const mobileInfo = MobileUtils.detect(); @@ -77,9 +75,6 @@ class App extends EventEmitter { MobileUtils.logMobileInfo(); } - // Example: Add some basic functionality - this.addExampleFeatures(); - // Initialize modules await this.initializeModules(); } @@ -89,10 +84,21 @@ class App extends EventEmitter { */ private async initializeModules(): Promise { try { - // Initialize example module - const exampleModule = new ExampleModule(); - await exampleModule.initialize(); - this.registerModule('example', exampleModule); + // Initialize TikTok Quick Block module + const tiktokModule = new TikTokQuickBlockModule(); + await tiktokModule.initialize(); + this.registerModule('tiktok-quick-block', tiktokModule); + + // Listen to TikTok module events + tiktokModule.on('userBlocked', ({ username, timestamp }) => { + console.log( + `🚫 User blocked: @${username} at ${new Date(timestamp).toLocaleString()}` + ); + }); + + tiktokModule.on('buttonInjected', ({ count }) => { + console.log(`✨ Quick block buttons injected: ${count}`); + }); // Initialize mobile module if on mobile device const mobileInfo = MobileUtils.detect(); @@ -111,91 +117,11 @@ class App extends EventEmitter { console.log(`📱 Orientation changed to: ${orientation}`); }); } - - // Listen to example module events - exampleModule.on('actionPerformed', ({ action, timestamp }) => { - console.log( - `📡 Module action received: ${action} at ${new Date(timestamp).toLocaleString()}` - ); - }); } catch (error) { console.error('❌ Failed to initialize modules:', error); } } - /** - * Example features to demonstrate the template - */ - private addExampleFeatures(): void { - // Add mobile-optimized styles - const mobileInfo = MobileUtils.detect(); - const baseCss = ` - .userscript-highlight { - background-color: yellow !important; - border: 2px solid red !important; - } - `; - - // Add mobile-specific styles if on mobile - const mobileCss = mobileInfo.isMobile - ? ` - .userscript-highlight { - padding: 8px !important; - border-radius: 4px !important; - font-size: 16px !important; /* Prevent zoom on iOS */ - } - ` - : ''; - - DOMUtils.addStyles(baseCss + mobileCss, 'userscript-styles'); - - // Example: Storage usage - const visitCount = (Storage.get('visitCount', 0) || 0) + 1; - Storage.set('visitCount', visitCount); - console.log(`📊 This is visit #${visitCount}`); - - // Example: Add menu command (with mobile detection) - if (typeof GM_registerMenuCommand !== 'undefined') { - GM_registerMenuCommand('Show Visit Count', () => { - const count = Storage.get('visitCount', 0); - - if (mobileInfo.isMobile) { - // Mobile-friendly notification - if (typeof GM_notification !== 'undefined') { - GM_notification(`Visit Count: ${count}`, 'UserScript Info', undefined, () => { - console.log('Notification clicked'); - }); - } else { - alert(`You have visited this page ${count} times!`); - } - } else { - // Desktop alert - alert(`You have visited this page ${count} times!`); - } - }); - - // Add mobile-specific menu command - if (mobileInfo.isMobile) { - GM_registerMenuCommand('Mobile Info', () => { - MobileUtils.logMobileInfo(); - - const info = ` -Device: ${mobileInfo.isAndroid ? 'Android' : mobileInfo.isIOS ? 'iOS' : 'Other'} -Touch Support: ${mobileInfo.hasTouch ? 'Yes' : 'No'} -UserScript Support: ${MobileUtils.supportsUserScripts() ? 'Yes' : 'No'} -Recommended Manager: ${MobileUtils.getRecommendedUserScriptManager()} - `; - - if (typeof GM_notification !== 'undefined') { - GM_notification(info, 'Mobile Device Info'); - } else { - alert(info); - } - }); - } - } - } - /** * Register a module */ diff --git a/src/modules/tiktok-quick-block.ts b/src/modules/tiktok-quick-block.ts new file mode 100644 index 0000000..4738f3f --- /dev/null +++ b/src/modules/tiktok-quick-block.ts @@ -0,0 +1,453 @@ +import { DOMUtils } from '@/utils/dom'; +import { EventEmitter } from '@/utils/events'; +import { Storage } from '@/utils/storage'; + +/** + * Events emitted by TikTokQuickBlock module + */ +interface TikTokQuickBlockEvents { + initialized: void; + userBlocked: { username: string; timestamp: number }; + buttonInjected: { count: number }; +} + +/** + * TikTok Quick Block Module + * + * Adds a quick block button to TikTok comments that allows blocking comment authors + * with a single click directly from the comment interface. + */ +export class TikTokQuickBlockModule extends EventEmitter { + private blockedUsers: Set = new Set(); + private processedComments: WeakSet = new WeakSet(); + private observer: MutationObserver | null = null; + private buttonCount = 0; + + constructor() { + super(); + } + + /** + * Initialize the module + */ + async initialize(): Promise { + console.log('🚀 TikTok Quick Block: Initializing...'); + + // Load blocked users from storage + this.loadBlockedUsers(); + + // Add custom styles + this.addStyles(); + + // Wait for the page to load and start observing + if (document.readyState === 'loading') { + await new Promise(resolve => { + document.addEventListener('DOMContentLoaded', resolve, { once: true }); + }); + } + + // Wait a bit for TikTok's dynamic content to load + await new Promise(resolve => setTimeout(resolve, 2000)); + + // Start observing for comments + this.startObserving(); + + // Process any existing comments + this.processExistingComments(); + + // Register menu commands + this.registerMenuCommands(); + + this.emit('initialized', undefined); + console.log('✅ TikTok Quick Block: Initialized successfully'); + } + + /** + * Load blocked users from storage + */ + private loadBlockedUsers(): void { + const blocked = Storage.get('tiktok_blocked_users', []); + this.blockedUsers = new Set(blocked || []); + console.log(`📋 Loaded ${this.blockedUsers.size} blocked users`); + } + + /** + * Save blocked users to storage + */ + private saveBlockedUsers(): void { + Storage.set('tiktok_blocked_users', Array.from(this.blockedUsers)); + } + + /** + * Add custom styles for the quick block button + */ + private addStyles(): void { + const css = ` + .tiktok-quick-block-btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 4px 12px; + margin-left: 8px; + font-size: 12px; + font-weight: 600; + color: #fff; + background: linear-gradient(135deg, #fe2c55 0%, #ff0050 100%); + border: none; + border-radius: 4px; + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; + box-shadow: 0 2px 4px rgba(254, 44, 85, 0.2); + } + + .tiktok-quick-block-btn:hover { + background: linear-gradient(135deg, #ff0050 0%, #d40042 100%); + box-shadow: 0 4px 8px rgba(254, 44, 85, 0.3); + transform: translateY(-1px); + } + + .tiktok-quick-block-btn:active { + transform: translateY(0); + box-shadow: 0 1px 2px rgba(254, 44, 85, 0.2); + } + + .tiktok-quick-block-btn.blocked { + background: linear-gradient(135deg, #666 0%, #444 100%); + cursor: default; + opacity: 0.7; + } + + .tiktok-quick-block-btn.blocked:hover { + background: linear-gradient(135deg, #666 0%, #444 100%); + transform: none; + } + + /* Notification styles */ + .tiktok-quick-block-notification { + position: fixed; + top: 80px; + right: 20px; + padding: 16px 24px; + background: rgba(0, 0, 0, 0.9); + color: white; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + z-index: 999999; + opacity: 0; + transform: translateX(400px); + transition: all 0.3s ease; + max-width: 300px; + } + + .tiktok-quick-block-notification.show { + opacity: 1; + transform: translateX(0); + } + + .tiktok-quick-block-notification.success { + background: linear-gradient(135deg, #00f2ea 0%, #00d4ff 100%); + color: #000; + } + + /* Mobile optimizations */ + @media (max-width: 768px) { + .tiktok-quick-block-btn { + padding: 6px 10px; + font-size: 11px; + margin-left: 4px; + } + + .tiktok-quick-block-notification { + top: 60px; + right: 10px; + left: 10px; + max-width: none; + text-align: center; + } + } + `; + + DOMUtils.addStyles(css, 'tiktok-quick-block-styles'); + } + + /** + * Start observing DOM changes for new comments + */ + private startObserving(): void { + this.observer = new MutationObserver(mutations => { + for (const mutation of mutations) { + if (mutation.addedNodes.length > 0) { + this.processExistingComments(); + } + } + }); + + this.observer.observe(document.body, { + childList: true, + subtree: true, + }); + + console.log('👀 Started observing for new comments'); + } + + /** + * Process all existing comments in the page + */ + private processExistingComments(): void { + // TikTok uses various selectors for comments + // We'll try multiple common selectors + const commentSelectors = [ + '[data-e2e="comment-item"]', + '[class*="Comment"]', + '[class*="comment"]', + 'div[class*="DivCommentItemContainer"]', + 'div[class*="CommentItem"]', + ]; + + let comments: Element[] = []; + for (const selector of commentSelectors) { + const found = Array.from(document.querySelectorAll(selector)); + if (found.length > 0) { + comments = found; + console.log(`📝 Found ${comments.length} comments using selector: ${selector}`); + break; + } + } + + if (comments.length === 0) { + // Try a more generic approach - look for elements that might contain usernames + console.log('⚠️ No comments found with known selectors, trying fallback...'); + return; + } + + let newButtons = 0; + for (const comment of comments) { + if (!this.processedComments.has(comment)) { + if (this.injectQuickBlockButton(comment)) { + newButtons++; + this.processedComments.add(comment); + } + } + } + + if (newButtons > 0) { + this.buttonCount += newButtons; + this.emit('buttonInjected', { count: this.buttonCount }); + console.log(`✨ Injected ${newButtons} quick block buttons (total: ${this.buttonCount})`); + } + } + + /** + * Inject quick block button into a comment element + */ + private injectQuickBlockButton(commentElement: Element): boolean { + try { + // Try to find the username/author element + const usernameSelectors = [ + '[data-e2e="comment-username"]', + 'a[class*="Username"]', + 'a[class*="user"]', + 'span[class*="Username"]', + 'span[class*="user"]', + ]; + + let usernameElement: Element | null = null; + for (const selector of usernameSelectors) { + usernameElement = commentElement.querySelector(selector); + if (usernameElement) break; + } + + if (!usernameElement) { + return false; + } + + // Extract username + const username = this.extractUsername(usernameElement); + if (!username) { + return false; + } + + // Check if already blocked + const isBlocked = this.blockedUsers.has(username); + + // Create the quick block button + const blockButton = DOMUtils.createElement('button', { + className: `tiktok-quick-block-btn ${isBlocked ? 'blocked' : ''}`, + textContent: isBlocked ? '🚫 Blocked' : '⚡ Quick Block', + title: isBlocked ? `${username} is already blocked` : `Quick block ${username}`, + onclick: (e: Event) => { + e.preventDefault(); + e.stopPropagation(); + if (!isBlocked) { + this.blockUser(username, commentElement); + } + }, + }); + + // Find the best place to inject the button + // Try to find an action bar or button container + const actionBarSelectors = [ + '[class*="ActionBar"]', + '[class*="action"]', + '[class*="button-container"]', + ]; + + let actionBar: Element | null = null; + for (const selector of actionBarSelectors) { + actionBar = commentElement.querySelector(selector); + if (actionBar) break; + } + + if (actionBar) { + actionBar.appendChild(blockButton); + } else { + // Fallback: insert after username + usernameElement.parentElement?.appendChild(blockButton); + } + + return true; + } catch (error) { + console.error('❌ Error injecting button:', error); + return false; + } + } + + /** + * Extract username from element + */ + private extractUsername(element: Element): string | null { + // Try textContent first + let username = element.textContent?.trim() || ''; + + // Remove @ symbol if present + username = username.replace(/^@/, ''); + + // Try href attribute if it's a link + if (!username && element instanceof HTMLAnchorElement) { + const href = element.getAttribute('href'); + if (href) { + const match = href.match(/@([^/?]+)/); + if (match) { + username = match[1]; + } + } + } + + return username || null; + } + + /** + * Block a user + */ + private blockUser(username: string, commentElement: Element): void { + console.log(`🚫 Blocking user: ${username}`); + + // Add to blocked users + this.blockedUsers.add(username); + this.saveBlockedUsers(); + + // Hide the comment + if (commentElement instanceof HTMLElement) { + commentElement.style.opacity = '0.3'; + commentElement.style.pointerEvents = 'none'; + commentElement.style.filter = 'blur(2px)'; + } + + // Update button state + const button = commentElement.querySelector('.tiktok-quick-block-btn'); + if (button) { + button.textContent = '🚫 Blocked'; + button.classList.add('blocked'); + } + + // Show notification + this.showNotification(`Successfully blocked @${username}`, 'success'); + + // Emit event + this.emit('userBlocked', { username, timestamp: Date.now() }); + + // Show GM notification if available + if (typeof GM_notification !== 'undefined') { + GM_notification( + `User @${username} has been blocked`, + 'TikTok Quick Block', + undefined, + undefined + ); + } + } + + /** + * Show a temporary notification + */ + private showNotification(message: string, type: 'success' | 'info' = 'info'): void { + const notification = DOMUtils.createElement('div', { + className: `tiktok-quick-block-notification ${type}`, + textContent: message, + }); + + document.body.appendChild(notification); + + // Show with animation + setTimeout(() => notification.classList.add('show'), 10); + + // Hide and remove + setTimeout(() => { + notification.classList.remove('show'); + setTimeout(() => DOMUtils.removeElement(notification), 300); + }, 3000); + } + + /** + * Register menu commands + */ + private registerMenuCommands(): void { + if (typeof GM_registerMenuCommand !== 'undefined') { + GM_registerMenuCommand('View Blocked Users', () => { + const count = this.blockedUsers.size; + const users = Array.from(this.blockedUsers).join('\n'); + alert( + `Blocked Users (${count}):\n\n${users || 'No users blocked yet'}\n\nTo unblock users, clear your browser data or use the "Clear Blocked Users" command.` + ); + }); + + GM_registerMenuCommand('Clear Blocked Users', () => { + if (confirm('Are you sure you want to clear all blocked users?')) { + this.blockedUsers.clear(); + this.saveBlockedUsers(); + this.showNotification('All blocked users cleared', 'success'); + // Reload to show comments again + location.reload(); + } + }); + + GM_registerMenuCommand('Show Statistics', () => { + const stats = ` +TikTok Quick Block Statistics + +Blocked Users: ${this.blockedUsers.size} +Quick Block Buttons: ${this.buttonCount} + +This session: +- Initialized: ✅ +- Observer: ${this.observer ? 'Active' : 'Inactive'} + `; + alert(stats); + }); + } + } + + /** + * Cleanup when module is destroyed + */ + public destroy(): void { + if (this.observer) { + this.observer.disconnect(); + this.observer = null; + } + console.log('🛑 TikTok Quick Block: Module destroyed'); + } +}