Awesome Cursor Rules Collection

Showing 673-684 of 2626 matches

Python
## Python Coding Guidelines

- **Python Version**: Use 3.11.x
- **Dependency Management**: Use Poetry (`pyproject.toml`)
- **Testing**: Use PyTest in `tests/`
- **IDE**: VSCode
- **Code Formatting**: Black
- **Linting**: Flake8
- **Static Type Checking**: Pyright
- **Data Validation**: Pydantic 2.9.x
- **Web Framework**: FastAPI
- **ORM**: SQLAlchemy
- **Database Migrations**: Alembic
- **CLI**: Click 8.1.x
- **Logging**: Loguru
- **Configuration**: PyYAML
- **Environment Management**: Poetry
- **Documentation**: Sphinx
- **Deployment**: Docker
- **CI/CD**: GitHub Actions


## Code Style

- Use snake case for identifiers; kebab case for filenames.
- Use f-strings for formatting; triple quotes for multi-line strings.
- Apply type hints for all functions, variables, class attributes, and parameters.
- Prefer `pydantic` models over dictionaries for data validation.
- Prefer composition over inheritance.
- Follow the single source of truth principle. Kiss (Keep It Simple Stupid) principle.
- Use enums for constants.
- Use type guards to validate data types.

## Project Structure

```
├── pyproject.toml
├── poetry.lock
├── src/
│   └── your_package/
│       ├── __init__.py
│       └── main.py
└── tests/
    ├── __init__.py
    └── test_main.py
```

## Coding Standards

- **PEP 8**: Follow for style (max 79 chars, spaces around operators, use `f-strings`).
- **Docstrings**: Required for all public functions/classes.

## Testing Practices

- **Independence**: Tests should not rely on external systems.
- **Coverage**: Aim for high coverage; use fixtures for setup.
docker
fastapi
python
raphaelmansuy/iteration_of_tought

Used in 1 repository

JavaScript
# Defra AI Code Review Frontend

## Project Context
A Web Application for submitting code repositories for review. Also provides a interface for reviewing the code reviews. A frontend for the defra code review API.

## Language
- JavaScript
- TypeScript (for type checking only)

## Tech Stack
- Node.js
- Hapi.js (server framework)
- GOV.UK Frontend (UI components)     
- Nunjucks (template engine)
- Webpack (bundling and asset management)
- Babel (JavaScript transpilation)
- Jest (testing framework)
- ESLint & Prettier (code formatting and linting)
- SCSS (styling)
- PostCSS (CSS processing)
- Stylelint (CSS linting)

## Development Workflow
- Use npm scripts for common tasks
- always run `npm run format` after making any file changes
- always run `npm run lint:fix` after making any file changes
- when you are done modifying all the files, then run `npm run test` to ensure that your changes have not broken any existing functionality

## Project Structure
```
/
├── src/                    # Source code
│   ├── client/            # Frontend assets and code
│   │   ├── common/        # Shared client utilities and helpers
│   │   ├── javascripts/   # Client-side JavaScript
│   │   └── stylesheets/   # SCSS stylesheets
│   ├── server/            # Backend Node.js application code
│   │   ├── common/        # Shared server utilities
│   │   ├── error/         # Error handling and pages
│   │   ├── health/        # Health check endpoints
│   │   ├── home/          # Home page routes and handlers
│   │   ├── index.js       # Server entry point
│   │   └── router.js      # Route definitions
│   ├── config/            # Application configuration
│   └── index.js           # Application entry point
├── compose/               # Docker compose configuration files
├── .github/               # GitHub workflows and templates
├── .jest/                 # Jest test configuration
├── .husky/                # Git hooks configuration
├── config files           # Various configuration files:
│   ├── .eslintrc.cjs     # ESLint configuration
│   ├── .prettierrc.js    # Prettier configuration
│   ├── babel.config.cjs  # Babel configuration
│   ├── jest.config.js    # Jest configuration
│   ├── webpack.config.js # Webpack configuration
│   └── tsconfig.json     # TypeScript configuration
└── package.json          # Project dependencies and scripts
```

Key directories and their purposes:
- `src/client/common`: Shared client-side utilities and helpers
- `src/client/javascripts`: Client-side JavaScript modules
- `src/client/stylesheets`: SCSS styling organized into components, core, helpers, and partials
- `src/server/common`: Shared server-side utilities
- `src/server/error`: Error handling and error pages
- `src/server/health`: Health check endpoints
- `src/server/home`: Home page routes and handlers
- `compose/`: Docker compose files for different environments
- `.github/`: CI/CD workflows and GitHub-specific configuration
- `.jest/`: Test configuration and setup files
- `.husky/`: Git hooks for maintaining code quality

## Coding Standards

## Style
### Code Style
- Use Standard JS for linting and formatting.
- Maintain consistent indentation and code structure.
- Avoid nested callbacks; prefer async/await.
- Format code using prettier and use the .prettierrc file.
- Use JSDoc comments for type annotations in .js files
- Use TypeScript for type checking only (no .ts files)
- Include comprehensive JSDoc type definitions for function parameters and returns
- Follow TypeScript-enhanced ESLint rules

### Styling
- Use SCSS for styling
- Follow GOV.UK Frontend naming conventions for components
- Organize styles into:
  - components/ - For reusable components
  - core/ - For core layout elements
  - helpers/ - For mixins and functions
  - partials/ - For page-specific styles
  - variables/ - For shared variables
- Use BEM-style naming with 'app-' prefix for custom components

## Rules
### General Guidelines
- Use vanilla JavaScript; avoid TypeScript.
- Do not use front-end frameworks like React, Angular, or Vue.
- Ensure all code follows progressive enhancement principles.
- Use clear and descriptive variable and function names.
- Document complex code with comments.
- Separate concerns by organizing code logically.

### Functions
- Use named functions instead of arrow functions for top-level declarations
- Use arrow functions for callbacks and anonymous functions
- Function names should be camelCase and descriptive of their purpose

### Imports/Exports
- Use named exports instead of default exports
- Group imports by type (core Node.js modules first, then external packages, then internal imports)
- Use absolute imports with the '~' alias for internal project files
- Include JSDoc import statements for types when needed

### Types
- Use JSDoc comments for type annotations rather than TypeScript syntax in .js files
- TypeScript is used for type checking only (no .ts files)
- Include comprehensive type definitions for function parameters and returns

### Module System
- Use ES Modules (import/export) instead of CommonJS (require/module.exports)
- Always use named exports instead of default exports
- Add .js extension to all import statements

### File Structure
- Group related functionality into directories (e.g., helpers, components)
- Use index.js files to aggregate and re-export from directories
- Keep files focused on a single responsibility
- Use .gitkeep for empty directories that need to be tracked

### Configuration
- Use convict for configuration management
- Do not embedd secrets in the codebase.
- Validate configuration on startup
- Separate configuration by concern (e.g., redis, session, logging)
- Use postcss.config.js for PostCSS configuration
- Use stylelint.config.js for CSS linting rules
- Use nodemon.json for development server settings

### Error Handling
- Use explicit error types
- Log errors appropriately using the logging system
- Include stack traces in development but not production

### Node.js Standards
- Do not store session state on the app server.
- Use distributed caches for session management.
- Follow the same standards for linting and formatting as front-end code.

## Testing
- Use Jest as the testing framework
- Include comprehensive test cases covering success and error scenarios
- Test the functionality; not the implementation
- Write tests that cover end-to-end functionality covering multipule units
- For apis, test the API calls with expected input and output
- For UI's, test the interface with expected behaviours 
- Mock external dependencies like databases, file systems, API's
- Maintain .jest/setup.js for global test configuration and mocks

## Logging
- Use pino as the logging framework
- Log levels controlled via environment variables
- Different log formats for development (pretty) and production (ECS)
- Include request ID in logs for tracing
- Redact sensitive information in production logs

## Git Usage

### Commit Message Prefixes
- `fix:` for bug fixes
- `feat:` for new features
- `perf:` for performance improvements
- `docs:` for documentation updates
- `style:` for formatting changes
- `refactor:` for code refactoring
- `test:` for adding missing tests
- `chore:` for maintenance tasks

### Commit Guidelines
- Use lowercase for messages
- Limit the commit message to one line, and no more than two concise, descriptive sentences
- Reference issue numbers when applicable

## Documentation
- Maintain clear README
- Use JSDoc for function and type documentation
- Document configuration options
- Include examples where appropriate

## Security
- Enable secure contexts in production
- Use TLS for Redis in production
- Implement proper session handling
- Set secure cookie flags in production
- Validate all inputs to prevent injection attacks.

## Nunjucks & GOV.UK Frontend Rules

### Template Structure
- Use `{% extends 'layouts/page.njk' %}` as the base template for all pages
- Place page-specific content within the `{% block content %}` block
- Use `govuk-` prefix for GOV.UK Frontend components
- Use `app-` prefix for custom components
- Keep templates DRY by using macros and includes

### Component Usage
- Import GOV.UK components globally in layout files using:
  ```njk
  {% from "govuk/components/[component-name]/macro.njk" import govuk[ComponentName] %}
  ```
- Import custom components globally in layout files using:
  ```njk
  {% from "[component-name]/macro.njk" import app[ComponentName] %}
  ```
- Follow GOV.UK Frontend component parameter structure:
  ```njk
  {{ govukComponent({
    key: value,
    classes: "additional-classes"
  }) }}
  ```

### Custom Components
- Create components in `src/server/common/components/[component-name]/`
- Include three files per component:
  1. `template.njk` - Component markup
  2. `macro.njk` - Component macro definition
  3. `_[component-name].scss` - Component styles
- Use BEM naming convention with 'app-' prefix
- Include data-testid attributes for testing

### Layout & Grid
- Use GOV.UK Frontend grid system:
  - `govuk-grid-row` for rows
  - `govuk-grid-column-*` for columns
- Follow GOV.UK Frontend spacing units using `govuk-spacing()` function
- Use GOV.UK Frontend typography classes (e.g., `govuk-body`, `govuk-heading-xl`)

### Styling
- Import GOV.UK Frontend styles using:
  ```scss
  @use "govuk-frontend" as *;
  ```
- Use GOV.UK Frontend color variables and functions
- Follow GOV.UK Frontend breakpoints using `govuk-media-query()`
- Namespace custom styles with 'app-' prefix

### Testing
- Use the provided `renderComponent()` helper for component testing
- Include data-testid attributes for component testing
- Test component variations and edge cases

### Configuration
- Configure Nunjucks environment with:
  - `trimBlocks: true`
  - `lstripBlocks: true`
  - `autoescape: true`
- Set up proper paths for GOV.UK Frontend templates
- Use filters for data formatting (e.g., dates, currency)
- Add custom globals when needed

### Best Practices
- Keep templates focused on presentation logic
- Use macros for reusable template patterns
- Follow progressive enhancement principles
- Maintain accessibility standards
- Use proper HTML5 semantic elements
- Include ARIA attributes where necessary

### New Page Template Structure
- Create new pages using the following template structure:
  1. Controller file (`controller.js`) - Handles route logic
  2. Controller test file (`controller.test.js`) - Tests for controller
  3. Route registration file (`index.js`) - Registers routes with Hapi
  4. View template (`index.njk`) - Page template following this structure:
  ```njk
  {% extends 'layouts/page.njk' %}

  {% block content %}
    {{ appHeading({
      text: heading,
      caption: "[caption here]"
    }) }}

    [html content here]

  {% endblock %}
  ```

- Each new page directory should include:
  - `controller.js` - Route handler logic
  - `controller.test.js` - Controller tests
  - `index.js` - Route registration
  - `index.njk` - Page template
- Pass page data from controller to template using h.view()
- Use consistent variable naming between controller and template
- Include comprehensive tests for new routes
angular
bun
docker
dockerfile
eslint
golang
java
javascript
+12 more
DEFRA/defra-ai-codereview-frontend

Used in 1 repository

C++
You are an AI assistant who specializes in Flutter.

Follow these steps to generate your response:

1. Analyze the conversation history:
- Review all previous interactions between you and the user.
- Identify recurring themes, topics, or concepts the user has asked about.
- Assess the user's current level of understanding based on their questions and responses.

2. Understand the user's prompt and level:
- Carefully examine the user's current prompt.
- Identify the main technical topic or concept they're asking about.
- Determine if there are any underlying concepts the user might be struggling with.
- Consider how this question relates to previous topics discussed in the conversation history.
- Estimate the user's current level of understanding on this specific topic.

3. Analyze the codebase:
<project-tree>
.
├── Makefile
├── README.md
├── analysis_options.yaml
├── cursorruler.py
├── example-repo.txt
├── kick_notifier.iml
├── lib
│   ├── main.dart
│   └── services
│       └── kick_service.dart
├── linux
│   ├── CMakeLists.txt
│   ├── flutter
│   │   ├── CMakeLists.txt
│   │   ├── ephemeral
│   │   ├── generated_plugin_registrant.cc
│   │   ├── generated_plugin_registrant.h
│   │   └── generated_plugins.cmake
│   └── runner
│       ├── CMakeLists.txt
│       ├── main.cc
│       ├── my_application.cc
│       └── my_application.h
├── macos
│   ├── Flutter
│   │   ├── Flutter-Debug.xcconfig
│   │   ├── Flutter-Release.xcconfig
│   │   ├── GeneratedPluginRegistrant.swift
│   │   └── ephemeral
│   │       ├── Flutter-Generated.xcconfig
│   │       └── flutter_export_environment.sh
│   ├── Podfile
│   ├── Runner
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets
│   │   │   └── AppIcon.appiconset
│   │   │       ├── Contents.json
│   │   │       ├── app_icon_1024.png
│   │   │       ├── app_icon_128.png
│   │   │       ├── app_icon_16.png
│   │   │       ├── app_icon_256.png
│   │   │       ├── app_icon_32.png
│   │   │       ├── app_icon_512.png
│   │   │       └── app_icon_64.png
│   │   ├── Base.lproj
│   │   │   └── MainMenu.xib
│   │   ├── Configs
│   │   │   ├── AppInfo.xcconfig
│   │   │   ├── Debug.xcconfig
│   │   │   ├── Release.xcconfig
│   │   │   └── Warnings.xcconfig
│   │   ├── DebugProfile.entitlements
│   │   ├── Info.plist
│   │   ├── MainFlutterWindow.swift
│   │   └── Release.entitlements
│   ├── Runner.xcodeproj
│   │   ├── project.pbxproj
│   │   ├── project.xcworkspace
│   │   │   └── xcshareddata
│   │   │       ├── IDEWorkspaceChecks.plist
│   │   │       └── swiftpm
│   │   │           └── configuration
│   │   └── xcshareddata
│   │       └── xcschemes
│   │           └── Runner.xcscheme
│   ├── Runner.xcworkspace
│   │   ├── contents.xcworkspacedata
│   │   └── xcshareddata
│   │       └── IDEWorkspaceChecks.plist
│   └── RunnerTests
│       └── RunnerTests.swift
├── pubspec.lock
├── pubspec.yaml
├── test
│   └── widget_test.dart
└── windows
    ├── CMakeLists.txt
    ├── flutter
    │   ├── CMakeLists.txt
    │   ├── ephemeral
    │   ├── generated_plugin_registrant.cc
    │   ├── generated_plugin_registrant.h
    │   └── generated_plugins.cmake
    └── runner
        ├── CMakeLists.txt
        ├── Runner.rc
        ├── flutter_window.cpp
        ├── flutter_window.h
        ├── main.cpp
        ├── resource.h
        ├── resources
        │   └── app_icon.ico
        ├── runner.exe.manifest
        ├── utils.cpp
        ├── utils.h
        ├── win32_window.cpp
        └── win32_window.h
</project-tree>
<codebase>

=== analysis_options.yaml ===

# This file configures the analyzer, which statically analyzes Dart code to # check for errors, warnings, and lints. # # The issues identified by the analyzer are surfaced in the UI of Dart-enabled # IDEs (https: # invoked from the command line by running `flutter analyze`. # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints # and their documentation is published at https: # # Instead of disabling a lint rule for the entire project in the # section below, it can also be suppressed for a single line of code # or a specific dart file by using the `// ignore: name_of_lint` and # `// ignore_for_file: name_of_lint` syntax on the line or in the file # producing the lint. rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # Additional information about this file can be found at # https:
=== lib/main.dart ===

import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'services/kick_service.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); final FlutterLocalNotificationsPlugin notifications = FlutterLocalNotificationsPlugin(); const initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher'); const initializationSettingsIOS = DarwinInitializationSettings(); const initializationSettings = InitializationSettings( android: initializationSettingsAndroid, iOS: initializationSettingsIOS, ); await notifications.initialize(initializationSettings); runApp(MyApp(notifications: notifications)); } class MyApp extends StatelessWidget { final FlutterLocalNotificationsPlugin notifications; const MyApp({super.key, required this.notifications}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Kick Notifier', theme: ThemeData( primarySwatch: Colors.blue, useMaterial3: true, ), home: HomeScreen(notifications: notifications), ); } } class HomeScreen extends StatefulWidget { final FlutterLocalNotificationsPlugin notifications; const HomeScreen({super.key, required this.notifications}); @override State<HomeScreen> createState() => _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateMixin { final _usernameController = TextEditingController(text: 'TechPong'); final _messageController = TextEditingController(); KickService? _kickService; bool _isMonitoring = false; final List<Map<String, String>> _messages = []; final List<String> _debugLogs = []; late TabController _tabController; @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); } @override void dispose() { _usernameController.dispose(); _messageController.dispose(); _tabController.dispose(); _kickService?.stop(); super.dispose(); } void _addMessage(String username, String content) { setState(() { _messages.insert(0, {'username': username, 'content': content}); }); } void _addDebugLog(String log) { setState(() { _debugLogs.insert(0, "${DateTime.now().toString().split('.')[0]} - $log"); }); } void _toggleMonitoring() async { if (_isMonitoring) { _kickService?.stop(); setState(() { _isMonitoring = false; _messages.clear(); _debugLogs.clear(); }); } else { if (_usernameController.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Please enter a username')), ); return; } _addDebugLog("Starting monitoring for ${_usernameController.text}"); final service = KickService( _usernameController.text, widget.notifications, onMessage: _addMessage, onDebugLog: _addDebugLog, ); setState(() { _kickService = service; _isMonitoring = true; }); await service.start(context); } } void _sendMessage() { if (_messageController.text.isNotEmpty) { _addMessage("Me", _messageController.text); _messageController.clear(); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Kick Notifier'), ), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ TextField( controller: _usernameController, decoration: const InputDecoration( labelText: 'Enter Kick username', border: OutlineInputBorder(), ), ), const SizedBox(height: 20), ElevatedButton( onPressed: _toggleMonitoring, style: ElevatedButton.styleFrom( backgroundColor: _isMonitoring ? Colors.red : Colors.green, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), ), child: Text(_isMonitoring ? 'Stop Monitoring' : 'Start Monitoring'), ), if (_isMonitoring) ...[ const SizedBox(height: 20), Text( 'Monitoring ${_usernameController.text}\'s chat', style: const TextStyle(color: Colors.grey), ), ], const SizedBox(height: 20), TabBar( controller: _tabController, tabs: const [ Tab(text: 'Chat'), Tab(text: 'Debug Logs'), ], ), Expanded( child: TabBarView( controller: _tabController, children: [ Column( children: [ Expanded( child: ListView.builder( reverse: true, itemCount: _messages.length, itemBuilder: (context, index) { final message = _messages[index]; return Card( margin: const EdgeInsets.symmetric(vertical: 4), child: ListTile( title: Text(message['username']!), subtitle: Text(message['content']!), ), ); }, ), ), Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( children: [ Expanded( child: TextField( controller: _messageController, decoration: const InputDecoration( hintText: 'Type a message...', border: OutlineInputBorder(), ), onSubmitted: (_) => _sendMessage(), ), ), IconButton( icon: const Icon(Icons.send), onPressed: _sendMessage, ), ], ), ), ], ), ListView.builder( reverse: true, itemCount: _debugLogs.length, itemBuilder: (context, index) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Text( _debugLogs[index], style: const TextStyle(fontFamily: 'monospace'), ), ); }, ), ], ), ), ], ), ), ); } }
=== lib/services/kick_service.dart ===

import 'dart:async'; import 'dart:convert'; import 'dart:html' as html; import 'package:http/http.dart' as http; import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart' show kIsWeb; class KickChannelInfo { final int chatroomId; final int? livestreamId; final int? viewers; KickChannelInfo({ required this.chatroomId, this.livestreamId, this.viewers, }); factory KickChannelInfo.fromJson(Map<String, dynamic> json) { return KickChannelInfo( chatroomId: json['chatroom']['id'], livestreamId: json['livestream']?['id'], viewers: json['livestream']?['viewers'], ); } } class KickService { WebSocketChannel? _channel; final String channelName; final FlutterLocalNotificationsPlugin notifications; final void Function(String username, String content)? onMessage; final void Function(String message)? onDebugLog; bool isConnected = false; int? _chatroomId; Timer? _reconnectTimer; Timer? _pingTimer; bool _pongReceived = true; static const _reconnectDelay = Duration(seconds: 5); static const String _appKey = '32cbd69e4b950bf97679'; static const String _cluster = 'us2'; static const String _version = '7.6.0'; KickService(this.channelName, this.notifications, {this.onMessage, this.onDebugLog}); void _log(String message) { print(message); onDebugLog?.call(message); } Future<KickChannelInfo> _getChannelInfo() async { final response = await http.get( Uri.parse('https://kick.com/api/v1/channels/$channelName'), headers: { 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'accept-language': 'en', 'sec-fetch-dest': 'document', 'sec-fetch-mode': 'navigate', 'sec-fetch-site': 'none', 'sec-fetch-user': '?1', 'upgrade-insecure-requests': '1', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', }, ); if (response.statusCode != 200) { _log( 'Request URL: ${Uri.parse('https: _log('Request headers: ${response.request?.headers}'); _log('Response status code: ${response.statusCode}'); _log('Response headers: ${response.headers}'); _log('Response body: ${response.body}'); throw Exception('Failed to get channel info: ${response.statusCode}'); } final data = json.decode(response.body); return KickChannelInfo.fromJson(data); } void _startPingTimer() { _pingTimer?.cancel(); _pongReceived = true; _pingTimer = Timer.periodic(const Duration(seconds: 10), (timer) { if (_pongReceived) { _pongReceived = false; _channel?.sink.add('{"event":"pusher:ping","data":{}}'); } else { _reconnect(); } }); } void _reconnect() { _log('Reconnecting...'); stop(); _reconnectTimer = Timer(_reconnectDelay, () => start(null)); } Future<void> start(BuildContext? context) async { try { _log('Starting service for channel: $channelName'); _log('Getting channel info...'); final channelInfo = await _getChannelInfo(); _chatroomId = channelInfo.chatroomId; _log('Got chatroom ID: $_chatroomId'); _log('Connecting to WebSocket...'); final wsUrl = 'wss://ws-$_cluster.pusher.com/app/$_appKey' '?protocol=7' '&client=js' '&version=$_version' '&flash=false'; _channel = WebSocketChannel.connect(Uri.parse(wsUrl)); _log('Subscribing to chatroom: $_chatroomId'); _channel?.sink.add(json.encode({ 'event': 'pusher:subscribe', 'data': {'auth': '', 'channel': 'chatrooms.$_chatroomId.v2'} })); _channel?.stream.listen( (message) => _handleMessage(message), onDone: () { _log('WebSocket connection closed'); isConnected = false; _reconnect(); }, onError: (error) { _log('WebSocket error: $error'); isConnected = false; _reconnect(); }, ); _startPingTimer(); isConnected = true; _log('Service started successfully'); } catch (e) { _log('Error starting service: $e'); isConnected = false; if (context != null && context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Failed to start monitoring: $e'), backgroundColor: Colors.red, duration: const Duration(seconds: 5), ), ); } _reconnect(); } } void _handleMessage(String message) { try { _log('Received WebSocket message: $message'); final data = json.decode(message); _log('Decoded event type: ${data['event']}'); if (data['event'] == 'pusher:pong') { _pongReceived = true; return; } if (data['event'] == 'App\\Events\\ChatMessageEvent') { _log('Found chat message event'); final chatData = json.decode(data['data']); final username = chatData['sender']['username']; final content = chatData['content']; _log('Processing message from $username: $content'); _showNotification(username, content); onMessage?.call(username, content); } } catch (e) { _log('Error handling message: $e'); } } Future<void> _showNotification(String username, String message) async { _log('Showing notification for $username: $message'); if (kIsWeb) { if (html.Notification.supported && html.Notification.permission == 'granted') { html.Notification(username, body: message, icon: 'icons/Icon-192.png'); } return; } const androidDetails = AndroidNotificationDetails( 'kick_chat', 'Kick Chat', channelDescription: 'Notifications for Kick chat messages', importance: Importance.max, priority: Priority.high, ); const iosDetails = DarwinNotificationDetails( presentAlert: true, presentBadge: true, presentSound: true, ); const details = NotificationDetails( android: androidDetails, iOS: iosDetails, ); try { await notifications.show( DateTime.now().millisecondsSinceEpoch.remainder(100000), username, message, details, ); _log('Notification shown successfully'); } catch (e) { _log('Error showing notification: $e'); } } void stop() { _log('Stopping service'); _pingTimer?.cancel(); _reconnectTimer?.cancel(); _channel?.sink.close(); isConnected = false; } }
=== pubspec.yaml ===

name: kick_notifier description: "A new Flutter project." # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 # followed by an optional build number separated by a +. # Both the version and the builder number may be overridden in flutter # build by specifying --build-name and --build-number, respectively. # In Android, build-name is used as versionName while build-number used as versionCode. # Read more about Android versioning at https: # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. # Read more about iOS versioning at # https: # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. version: 1.0.0+1 environment: sdk: '>=3.0.0 <4.0.0' # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions # consider running `flutter pub upgrade --major-versions`. Alternatively, # dependencies can be manually updated by changing the version numbers below to # the latest version available on pub.dev. To see which dependencies have newer # versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 web_socket_channel: ^2.4.0 http: ^1.1.0 flutter_local_notifications: ^15.1.0+1 webview_flutter: ^4.4.2 dev_dependencies: flutter_test: sdk: flutter # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^2.0.0 # For information on the generic Dart part of this file, see the # following page: https: # The following section is specific to Flutter packages. flutter: # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see # https: # For details regarding adding assets from package dependencies, see # https: # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts from package dependencies, # see https:
=== test/widget_test.dart ===

import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:kick_notifier/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { await tester.pumpWidget(const MyApp()); expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); await tester.tap(find.byIcon(Icons.add)); await tester.pump(); expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); }); }
</codebase>

- Review the codebase to understand the context of the user's prompt.
- Identify any relevant files, functions, or concepts that are related to the user's prompt.
- Consider how the user's prompt fits into the overall codebase and architecture.

4. Prepare your response:
- Start with a brief introduction to the topic.
- Structure your explanation with gradually increasing complexity, tailored to the user's estimated
level.
- Include real-world examples and implementations to illustrate the concept.
- Compare and contrast with similar concepts or technologies when relevant.
- Mention tips, tricks, best practices, and common pitfalls related to the topic.
- If applicable, suggest memorization techniques for key points.

Remember:
- Always prioritize clarity and relevance in your explanations.
- Use appropriate technical terminology, but be sure to explain any complex terms.
- If you're unsure about any aspect of the user's question, ask for clarification before providing
an explanation.
- Encourage the user to ask follow-up questions if any part of your explanation is unclear.

If applicable, follow these special instructions for shell scripts:
- Use only Unix-style line endings (LF)
- Avoid special Unicode characters
- Include only ASCII-compatible quotes and brackets
- Is properly formatted for direct use in a bash script

<tool-versions>
pnpm 9.12.1
nodejs 22.9.0
flutter 3.27.1-stable
java temurin-17.0.9+9
</tool-versions>


<example-repo>
Directory Structure:
Directory Structure:

└── ./
    └── Moblin
        ├── Platforms
        │   └── Kick
        │       ├── KickChannel.swift
        │       ├── KickPusher.swift
        │       └── KickViewers.swift
        └── Various
            ├── ChatBotCommand.swift
            ├── ChatTextToSpeech.swift
            ├── UrlSession.swift
            ├── Utils.swift
            ├── WebSocetClient.swift
            └── WebSocketClient.swift



---
File: /Moblin/Platforms/Kick/KickChannel.swift
---

import Foundation

struct KickLivestream: Codable {
    // periphery:ignore
    let id: Int
    let viewers: Int
}

struct KickChatroom: Codable {
    let id: Int
}

struct KickChannel: Codable {
    // periphery:ignore
    let slug: String
    let chatroom: KickChatroom
    let livestream: KickLivestream?
}

func getKickChannelInfo(channelName: String) async throws -> KickChannel {
    guard let url = URL(string: "https://kick.com/api/v1/channels/\(channelName)") else {
        throw "Invalid URL"
    }
    let (data, response) = try await httpGet(from: url)
    if !response.isSuccessful {
        throw "Not successful"
    }
    return try JSONDecoder().decode(KickChannel.self, from: data)
}

func getKickChannelInfo(channelName: String, onComplete: @escaping (KickChannel?) -> Void) {
    guard let url = URL(string: "https://kick.com/api/v1/channels/\(channelName)") else {
        onComplete(nil)
        return
    }
    let request = URLRequest(url: url)
    URLSession.shared.dataTask(with: request) { data, response, error in
        guard error == nil, let data, response?.http?.isSuccessful == true else {
            onComplete(nil)
            return
        }
        onComplete(try? JSONDecoder().decode(KickChannel.self, from: data))
    }
    .resume()
}



---
File: /Moblin/Platforms/Kick/KickPusher.swift
---

import Foundation

private struct Badge: Decodable {
    var type: String
}

private struct Identity: Decodable {
    var color: String
    var badges: [Badge]
}

private struct Sender: Decodable {
    var username: String
    var identity: Identity
}

private struct ChatMessage: Decodable {
    var content: String
    var sender: Sender

    func isModerator() -> Bool {
        return sender.identity.badges.contains(where: { $0.type == "moderator" })
    }

    func isSubscriber() -> Bool {
        return sender.identity.badges.contains(where: { $0.type == "subscriber" })
    }
}

private func decodeEvent(message: String) throws -> (String, String) {
    if let jsonData = message.data(using: String.Encoding.utf8) {
        let data = try JSONSerialization.jsonObject(
            with: jsonData,
            options: JSONSerialization.ReadingOptions.mutableContainers
        )
        if let jsonResult: NSDictionary = data as? NSDictionary {
            if let type: String = jsonResult["event"] as? String {
                if let data: String = jsonResult["data"] as? String {
                    return (type, data)
                }
            }
        }
    }
    throw "Failed to get message event type"
}

private func decodeChatMessage(data: String) throws -> ChatMessage {
    return try JSONDecoder().decode(
        ChatMessage.self,
        from: data.data(using: String.Encoding.utf8)!
    )
}

private var url =
    URL(
        string: "wss://ws-us2.pusher.com/app/32cbd69e4b950bf97679?protocol=7&client=js&version=7.6.0&flash=false"
    )!

protocol KickOusherDelegate: AnyObject {
    func kickPusherMakeErrorToast(title: String, subTitle: String?)
    func kickPusherAppendMessage(
        user: String,
        userColor: RgbColor?,
        segments: [ChatPostSegment],
        isSubscriber: Bool,
        isModerator: Bool
    )
}

final class KickPusher: NSObject {
    private var channelName: String
    private var channelId: String
    private var webSocket: WebSocketClient
    private var emotes: Emotes
    private let settings: SettingsStreamChat
    private var gotInfo = false
    private weak var delegate: (any KickOusherDelegate)?

    init(delegate: KickOusherDelegate, channelId: String, channelName: String, settings: SettingsStreamChat) {
        self.delegate = delegate
        self.channelId = channelId
        self.channelName = channelName
        self.settings = settings.clone()
        emotes = Emotes()
        webSocket = .init(url: url)
    }

    func start() {
        logger.debug("kick: Start")
        stopInternal()
        if channelName.isEmpty {
            connect()
        } else {
            getInfoAndConnect()
        }
    }

    private func getInfoAndConnect() {
        logger.debug("kick: Get info and connect")
        getKickChannelInfo(channelName: channelName) { [weak self] channelInfo in
            guard let self else {
                return
            }
            DispatchQueue.main.async {
                guard !self.gotInfo else {
                    return
                }
                guard let channelInfo else {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
                        self.getInfoAndConnect()
                    }
                    return
                }
                self.gotInfo = true
                self.channelId = String(channelInfo.chatroom.id)
                self.connect()
            }
        }
    }

    private func connect() {
        emotes.stop()
        emotes.start(
            platform: .kick,
            channelId: channelId,
            onError: handleError,
            onOk: handleOk,
            settings: settings
        )
        webSocket = .init(url: url)
        webSocket.delegate = self
        webSocket.start()
    }

    func stop() {
        logger.debug("kick: Stop")
        stopInternal()
    }

    func stopInternal() {
        emotes.stop()
        webSocket.stop()
        gotInfo = false
    }

    func isConnected() -> Bool {
        return webSocket.isConnected()
    }

    func hasEmotes() -> Bool {
        return emotes.isReady()
    }

    private func handleError(title: String, subTitle: String) {
        DispatchQueue.main.async {
            self.delegate?.kickPusherMakeErrorToast(title: title, subTitle: subTitle)
        }
    }

    private func handleOk(title: String) {
        DispatchQueue.main.async {
            self.delegate?.kickPusherMakeErrorToast(title: title, subTitle: nil)
        }
    }

    private func handleMessage(message: String) {
        do {
            let (type, data) = try decodeEvent(message: message)
            if type == "App\\Events\\ChatMessageEvent" {
                try handleChatMessageEvent(data: data)
            } else {
                logger.debug("kick: pusher: \(channelId): Unsupported type: \(type)")
            }
        } catch {
            logger
                .error("""
                kick: pusher: \(channelId): Failed to process \
                message \"\(message)\" with error \(error)
                """)
        }
    }

    private func handleChatMessageEvent(data: String) throws {
        let message = try decodeChatMessage(data: data)
        var segments: [ChatPostSegment] = []
        var id = 0
        for var segment in createKickSegments(message: message.content, id: &id) {
            if let text = segment.text {
                segments += emotes.createSegments(text: text, id: &id)
                segment.text = nil
            }
            if segment.text != nil || segment.url != nil {
                segments.append(segment)
            }
        }
        delegate?.kickPusherAppendMessage(
            user: message.sender.username,
            userColor: RgbColor.fromHex(string: message.sender.identity.color),
            segments: segments,
            isSubscriber: message.isSubscriber(),
            isModerator: message.isModerator()
        )
    }

    private func sendMessage(message: String) {
        logger.debug("kick: pusher: \(channelId): Sending \(message)")
        webSocket.send(string: message)
    }

    private func createKickSegments(message: String, id: inout Int) -> [ChatPostSegment] {
        var segments: [ChatPostSegment] = []
        var startIndex = message.startIndex
        for match in message[startIndex...].matches(of: /\[emote:(\d+):[^\]]+\]/) {
            let emoteId = match.output.1
            let textBeforeEmote = message[startIndex ..< match.range.lowerBound]
            let url = URL(string: "https://files.kick.com/emotes/\(emoteId)/fullsize")
            segments += makeChatPostTextSegments(text: String(textBeforeEmote), id: &id)
            segments.append(ChatPostSegment(id: id, url: url))
            id += 1
            startIndex = match.range.upperBound
        }
        if startIndex != message.endIndex {
            segments += makeChatPostTextSegments(text: String(message[startIndex...]), id: &id)
        }
        return segments
    }
}

extension KickPusher: WebSocketClientDelegate {
    func webSocketClientConnected(_: WebSocketClient) {
        logger.debug("kick: Connected")
        sendMessage(
            message: """
            {\"event\":\"pusher:subscribe\",
             \"data\":{\"auth\":\"\",\"channel\":\"chatrooms.\(channelId).v2\"}}
            """
        )
    }

    func webSocketClientDisconnected(_: WebSocketClient) {
        logger.debug("kick: Disconnected")
    }

    func webSocketClientReceiveMessage(_: WebSocketClient, string: String) {
        handleMessage(message: string)
    }
}



---
File: /Moblin/Platforms/Kick/KickViewers.swift
---

import Foundation

class KickViewers {
    private var task: Task<Void, Error>?
    var numberOfViewers: Int?

    func start(channelName: String) {
        task = Task.init {
            var delay = 1
            while true {
                do {
                    try await sleep(seconds: delay)
                    let info = try await getKickChannelInfo(channelName: channelName)
                    await self.setNumberOfViewers(value: info.livestream?.viewers)
                } catch {}
                if Task.isCancelled {
                    await self.setNumberOfViewers(value: nil)
                    break
                }
                delay = 30
            }
        }
    }

    private func setNumberOfViewers(value: Int?) async {
        await MainActor.run {
            self.numberOfViewers = value
        }
    }

    func stop() {
        task?.cancel()
        task = nil
    }
}



---
File: /Moblin/Various/ChatBotCommand.swift
---

import Collections

struct ChatBotMessage {
    let platform: Platform
    let user: String?
    let isModerator: Bool
    let isSubscriber: Bool
    let userId: String?
    let segments: [ChatPostSegment]
}

class ChatBotCommand {
    let message: ChatBotMessage
    private var parts: Deque<String> = []

    init?(message: ChatBotMessage) {
        self.message = message
        guard message.segments.count > 1 else {
            return nil
        }
        for segment in message.segments.suffix(from: 1) {
            if let text = segment.text {
                parts.append(text.trim())
            }
        }
    }

    func popFirst() -> String? {
        return parts.popFirst()
    }

    func rest() -> String {
        return parts.joined(separator: " ")
    }

    func user() -> String? {
        return message.user
    }
}



---
File: /Moblin/Various/ChatTextToSpeech.swift
---

import AVFAudio
import Collections
import NaturalLanguage

private let textToSpeechDispatchQueue = DispatchQueue(label: "com.eerimoq.textToSpeech", qos: .utility)

private struct TextToSpeechMessage {
    let user: String
    let message: String
    let isRedemption: Bool
}

private let saysByLanguage = [
    "en": "says",
    "sv": "säger",
    "no": "sier",
    "es": "dice",
    "de": "sagt",
    "fr": "dit",
    "pl": "mówi",
    "vi": "nói",
    "nl": "zegt",
    "zh": "说",
    "ko": "라고",
    "ru": "говорит",
    "uk": "каже",
]

class ChatTextToSpeech: NSObject {
    private var rate: Float = 0.4
    private var volume: Float = 0.6
    private var sayUsername: Bool = false
    private var detectLanguagePerMessage: Bool = false
    private var voices: [String: String] = [:]
    private var messageQueue: Deque<TextToSpeechMessage> = .init()
    private var synthesizer = AVSpeechSynthesizer()
    private var recognizer = NLLanguageRecognizer()
    private var latestUserThatSaidSomething: String?
    private var sayLatestUserThatSaidSomethingAgain = ContinuousClock.now
    private var filterEnabled: Bool = true
    private var filterMentionsEnabled: Bool = true
    private var streamerMentions: [String] = []
    private var running = true

    private func isFilteredOut(message: String) -> Bool {
        if isFilteredOutFilter(message: message) {
            return true
        }
        if isFilteredOutFilterMentions(message: message) {
            return true
        }
        return false
    }

    private func isFilteredOutFilter(message: String) -> Bool {
        if !filterEnabled {
            return false
        }
        let probability = recognizer.languageHypotheses(withMaximum: 1).first?.value ?? 0.0
        if probability < 0.7 && message.count > 30 {
            return true
        }
        if message.hasPrefix("!") {
            return true
        }
        if message.contains("https") {
            return true
        }
        return false
    }

    private func isFilteredOutFilterMentions(message: String) -> Bool {
        if !filterMentionsEnabled {
            return false
        }
        if message.starts(with: "@") || message.contains(" @") {
            for streamerMention in streamerMentions {
                if let range = message.range(of: streamerMention) {
                    if isStreamerMention(
                        message: message,
                        mentionLowerBound: range.lowerBound,
                        mentionUpperBound: range.upperBound
                    ) {
                        return false
                    }
                }
            }
            return true
        }
        return false
    }

    private func isStreamerMention(
        message: String,
        mentionLowerBound: String.Index,
        mentionUpperBound: String.Index
    ) -> Bool {
        // There is always a space at the end of the message, so this should never happen.
        guard mentionUpperBound < message.endIndex else {
            return false
        }
        if mentionLowerBound > message.startIndex {
            if message[message.index(before: mentionLowerBound)] != " " {
                return false
            }
        }
        if message[mentionUpperBound] != " " {
            return false
        }
        return true
    }

    private func getSays(_ language: String) -> String {
        return saysByLanguage[language] ?? ""
    }

    private func getVoice(message: String) -> (AVSpeechSynthesisVoice?, String)? {
        recognizer.reset()
        recognizer.processString(message)
        guard !isFilteredOut(message: message) else {
            return nil
        }
        var language = recognizer.dominantLanguage?.rawValue
        if !detectLanguagePerMessage || language == nil {
            language = Locale.current.language.languageCode?.identifier
        }
        guard let language else {
            return nil
        }
        if let voiceIdentifier = voices[language] {
            return (AVSpeechSynthesisVoice(identifier: voiceIdentifier), getSays(language))
        } else if let voice = AVSpeechSynthesisVoice.speechVoices()
            .filter({ $0.language.starts(with: language) }).first
        {
            return (AVSpeechSynthesisVoice(identifier: voice.identifier), getSays(language))
        }
        return nil
    }

    private func trySayNextMessage() {
        guard !synthesizer.isSpeaking else {
            return
        }
        guard let message = messageQueue.popFirst() else {
            return
        }
        guard let (voice, says) = getVoice(message: message.message) else {
            return
        }
        guard let voice else {
            return
        }
        let text: String
        let now = ContinuousClock.now
        if message.isRedemption {
            text = "\(message.user) \(message.message)"
        } else if !shouldSayUser(user: message.user, now: now) || !sayUsername {
            text = message.message
        } else {
            text = String(localized: "\(message.user) \(says): \(message.message)")
        }
        let utterance = AVSpeechUtterance(string: text)
        utterance.rate = rate
        utterance.pitchMultiplier = 0.8
        utterance.preUtteranceDelay = 0.05
        utterance.volume = volume
        utterance.voice = voice
        synthesizer.speak(utterance)
        latestUserThatSaidSomething = message.user
        sayLatestUserThatSaidSomethingAgain = now.advanced(by: .seconds(30))
    }

    private func shouldSayUser(user: String, now: ContinuousClock.Instant) -> Bool {
        if user != latestUserThatSaidSomething {
            return true
        }
        if now > sayLatestUserThatSaidSomethingAgain {
            return true
        }
        return false
    }

    func say(user: String, message: String, isRedemption: Bool) {
        textToSpeechDispatchQueue.async {
            guard self.running else {
                return
            }
            self.messageQueue.append(.init(user: user, message: message, isRedemption: isRedemption))
            self.trySayNextMessage()
        }
    }

    func setRate(rate: Float) {
        textToSpeechDispatchQueue.async {
            self.rate = rate
        }
    }

    func setVolume(volume: Float) {
        textToSpeechDispatchQueue.async {
            self.volume = volume
        }
    }

    func setVoices(voices: [String: String]) {
        textToSpeechDispatchQueue.async {
            self.voices = voices
        }
    }

    func setSayUsername(value: Bool) {
        textToSpeechDispatchQueue.async {
            self.sayUsername = value
        }
    }

    func setFilter(value: Bool) {
        textToSpeechDispatchQueue.async {
            self.filterEnabled = value
        }
    }

    func setFilterMentions(value: Bool) {
        textToSpeechDispatchQueue.async {
            self.filterMentionsEnabled = value
        }
    }

    func setStreamerMentions(streamerMentions: [String]) {
        textToSpeechDispatchQueue.async {
            self.streamerMentions = streamerMentions
        }
    }

    func setDetectLanguagePerMessage(value: Bool) {
        textToSpeechDispatchQueue.async {
            self.detectLanguagePerMessage = value
        }
    }

    func reset(running: Bool) {
        textToSpeechDispatchQueue.async {
            self.running = running
            self.synthesizer.stopSpeaking(at: .word)
            self.latestUserThatSaidSomething = nil
            self.messageQueue.removeAll()
            self.synthesizer = AVSpeechSynthesizer()
            self.synthesizer.delegate = self
            self.recognizer = NLLanguageRecognizer()
        }
    }

    func skipCurrentMessage() {
        textToSpeechDispatchQueue.async {
            self.synthesizer.stopSpeaking(at: .word)
            self.trySayNextMessage()
        }
    }
}

extension ChatTextToSpeech: AVSpeechSynthesizerDelegate {
    func speechSynthesizer(_: AVSpeechSynthesizer, didFinish _: AVSpeechUtterance) {
        textToSpeechDispatchQueue.async {
            self.trySayNextMessage()
        }
    }
}



---
File: /Moblin/Various/UrlSession.swift
---

import Foundation

extension URLSession {
    static func create(httpProxy: HttpProxy?) -> URLSession {
        if let httpProxy {
            if #available(iOS 17, *) {
                let configuration = URLSessionConfiguration.default
                configuration.proxyConfigurations = [
                    .init(httpCONNECTProxy: .hostPort(
                        host: .init(httpProxy.host),
                        port: .init(integerLiteral: httpProxy.port)
                    )),
                ]
                return URLSession(configuration: configuration)
            }
        }
        return URLSession.shared
    }
}



---
File: /Moblin/Various/Utils.swift
---

import AVKit
import MapKit
import MetalPetal
import SwiftUI
import WeatherKit

extension UIImage {
    func scalePreservingAspectRatio(targetSize: CGSize) -> UIImage {
        let widthRatio = targetSize.width / size.width
        let heightRatio = targetSize.height / size.height
        let scaleFactor = min(widthRatio, heightRatio)
        let scaledImageSize = CGSize(
            width: size.width * scaleFactor,
            height: size.height * scaleFactor
        )
        let renderer = UIGraphicsImageRenderer(
            size: scaledImageSize
        )
        let scaledImage = renderer.image { _ in
            self.draw(in: CGRect(
                origin: .zero,
                size: scaledImageSize
            ))
        }
        return scaledImage
    }

    func resize(height: CGFloat) -> UIImage {
        let size = CGSize(width: size.width * (height / size.height), height: height)
        let format = UIGraphicsImageRendererFormat()
        format.scale = 1
        let image = UIGraphicsImageRenderer(size: size, format: format).image { _ in
            draw(in: CGRect(origin: .zero, size: size))
        }

        return image.withRenderingMode(renderingMode)
    }
}

func widgetImage(widget: SettingsWidget) -> String {
    switch widget.type {
    case .image:
        return "photo"
    case .videoEffect:
        return "camera.filters"
    case .browser:
        return "globe"
    case .text:
        return "textformat"
    case .crop:
        return "crop"
    case .map:
        return "map"
    case .scene:
        return "photo.on.rectangle"
    case .qrCode:
        return "qrcode"
    case .alerts:
        return "megaphone"
    case .videoSource:
        return "video"
    case .scoreboard:
        return "rectangle.split.2x1"
    }
}

extension Data {
    static func random(length: Int) -> Data {
        return Data((0 ..< length).map { _ in UInt8.random(in: UInt8.min ... UInt8.max) })
    }
}

func randomString() -> String {
    return Data.random(length: 64).base64EncodedString()
}

func randomHumanString() -> String {
    return Data.random(length: 15).base64EncodedString().replacingOccurrences(
        of: "[+/=]",
        with: "",
        options: .regularExpression
    )
}

func isGoodPassword(password: String) -> Bool {
    guard password.count >= 16 else {
        return false
    }
    var seenCharacters = ""
    for character in password {
        if seenCharacters.contains(character) {
            return false
        }
        seenCharacters.append(character)
    }
    guard password.contains(/\d/) else {
        return false
    }
    return true
}

func randomGoodPassword() -> String {
    while true {
        let password = randomHumanString()
        if isGoodPassword(password: password) {
            return password
        }
    }
}

func getOrientation() -> UIDeviceOrientation {
    let orientation = UIDevice.current.orientation
    if orientation != .unknown {
        return orientation
    }
    let interfaceOrientation = UIApplication.shared.connectedScenes
        .first(where: { $0 is UIWindowScene })
        .flatMap { $0 as? UIWindowScene }?.interfaceOrientation
    switch interfaceOrientation {
    case .landscapeLeft:
        return .landscapeRight
    case .landscapeRight:
        return .landscapeLeft
    default:
        return .unknown
    }
}

extension AVCaptureDevice {
    func getZoomFactorScale(hasUltraWideCamera: Bool) -> Float {
        if hasUltraWideCamera {
            switch deviceType {
            case .builtInTripleCamera, .builtInDualWideCamera, .builtInUltraWideCamera:
                return 0.5
            case .builtInTelephotoCamera:
                return (virtualDeviceSwitchOverVideoZoomFactors.last?.floatValue ?? 10.0) / 2
            default:
                return 1.0
            }
        } else {
            switch deviceType {
            case .builtInTelephotoCamera:
                return virtualDeviceSwitchOverVideoZoomFactors.last?.floatValue ?? 2.0
            default:
                return 1.0
            }
        }
    }

    func getUIZoomRange(hasUltraWideCamera: Bool) -> (Float, Float) {
        let factor = getZoomFactorScale(hasUltraWideCamera: hasUltraWideCamera)
        return (Float(minAvailableVideoZoomFactor) * factor, Float(maxAvailableVideoZoomFactor) * factor)
    }

    var fps: (Double, Double) {
        (1 / activeVideoMinFrameDuration.seconds, 1 / activeVideoMaxFrameDuration.seconds)
    }
}

func cameraName(device: AVCaptureDevice?) -> String {
    guard let device else {
        return ""
    }
    if ProcessInfo().isiOSAppOnMac {
        return device.localizedName
    } else {
        switch device.deviceType {
        case .builtInTripleCamera:
            return String(localized: "Triple (auto)")
        case .builtInDualCamera:
            return String(localized: "Dual (auto)")
        case .builtInDualWideCamera:
            return String(localized: "Wide dual (auto)")
        case .builtInUltraWideCamera:
            return String(localized: "Ultra wide")
        case .builtInWideAngleCamera:
            return String(localized: "Wide")
        case .builtInTelephotoCamera:
            return String(localized: "Telephoto")
        default:
            return device.localizedName
        }
    }
}

func hasUltraWideBackCamera() -> Bool {
    return AVCaptureDevice.default(.builtInUltraWideCamera, for: .video, position: .back) != nil
}

func getBestBackCameraDevice() -> AVCaptureDevice? {
    var device = AVCaptureDevice.default(.builtInTripleCamera, for: .video, position: .back)
    if device == nil {
        device = AVCaptureDevice.default(.builtInDualWideCamera, for: .video, position: .back)
    }
    if device == nil {
        device = AVCaptureDevice.default(.builtInDualCamera, for: .video, position: .back)
    }
    if device == nil {
        device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
    }
    return device
}

func getBestFrontCameraDevice() -> AVCaptureDevice? {
    return AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front)
}

func getBestBackCameraId() -> String {
    guard let device = getBestBackCameraDevice() else {
        return ""
    }
    return device.uniqueID
}

func getBestFrontCameraId() -> String {
    guard let device = getBestFrontCameraDevice() else {
        return ""
    }
    return device.uniqueID
}

func openUrl(url: String) {
    UIApplication.shared.open(URL(string: url)!)
}

extension UIDevice {
    static func vibrate() {
        AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
    }
}

extension URL {
    var attributes: [FileAttributeKey: Any]? {
        do {
            return try FileManager.default.attributesOfItem(atPath: path)
        } catch {
            logger.info("file-system: Failed to get attributes for file \(self)")
        }
        return nil
    }

    var fileSize: UInt64 {
        return attributes?[.size] as? UInt64 ?? UInt64(0)
    }

    func remove() {
        do {
            try FileManager.default.removeItem(at: self)
        } catch {
            logger.info("file-system: Failed to remove file \(self)")
        }
    }
}

private var thumbnails: [URL: UIImage] = [:]

func createThumbnail(path: URL) -> UIImage? {
    if let thumbnail = thumbnails[path] {
        return thumbnail
    }
    do {
        let asset = AVURLAsset(url: path, options: nil)
        let imgGenerator = AVAssetImageGenerator(asset: asset)
        imgGenerator.appliesPreferredTrackTransform = true
        let cgImage = try imgGenerator.copyCGImage(at: CMTimeMake(value: 0, timescale: 1), actualTime: nil)
        let thumbnail = UIImage(cgImage: cgImage)
        thumbnails[path] = thumbnail
        return thumbnail
    } catch {
        logger.info("Failed to create thumbnail with error \(error)")
        return nil
    }
}

extension SettingsPrivacyRegion {
    func contains(coordinate: CLLocationCoordinate2D) -> Bool {
        cos(toRadians(degrees: latitude - coordinate.latitude)) >
            cos(toRadians(degrees: latitudeDelta / 2.0)) &&
            cos(toRadians(degrees: longitude - coordinate.longitude)) >
            cos(toRadians(degrees: longitudeDelta / 2.0))
    }
}

func toRadians(degrees: Double) -> Double {
    return degrees * .pi / 180
}

func toLatitudeDeltaDegrees(meters: Double) -> Double {
    return 360 * meters / 40_075_000
}

func toLongitudeDeltaDegrees(meters: Double, latitudeDegrees: Double) -> Double {
    return 360 * meters / (40_075_000 * cos(toRadians(degrees: latitudeDegrees)))
}

extension CLLocationCoordinate2D {
    func translateMeters(x: Double, y: Double) -> CLLocationCoordinate2D {
        let latitudeDelta = toLatitudeDeltaDegrees(meters: y)
        var newLatitude = (latitude < 0 ? 360 + latitude : latitude) + latitudeDelta
        newLatitude -= Double(360 * (Int(newLatitude) / 360))
        if newLatitude > 270 {
            newLatitude -= 360
        } else if newLatitude > 90 {
            newLatitude = 180 - newLatitude
        }
        let longitudeDelta = toLongitudeDeltaDegrees(meters: x, latitudeDegrees: latitude)
        var newLongitude = (longitude < 0 ? 360 + longitude : longitude) + longitudeDelta
        newLongitude -= Double(360 * (Int(newLongitude) / 360))
        if newLongitude > 180 {
            newLongitude = newLongitude - 360
        }
        return .init(latitude: newLatitude, longitude: newLongitude)
    }
}

extension MKCoordinateRegion: @retroactive Equatable {
    public static func == (lhs: MKCoordinateRegion, rhs: MKCoordinateRegion) -> Bool {
        if lhs.center.latitude != rhs.center.latitude || lhs.center.longitude != rhs.center.longitude {
            return false
        }
        if lhs.span.latitudeDelta != rhs.span.latitudeDelta || lhs.span.longitudeDelta != rhs.span
            .longitudeDelta
        {
            return false
        }
        return true
    }
}

struct WidgetCrop {
    let position: CGPoint
    let crop: CGRect
}

func hasAppleLog() -> Bool {
    if #available(iOS 17.0, *) {
        for format in getBestBackCameraDevice()?.formats ?? []
            where format.supportedColorSpaces.contains(.appleLog)
        {
            return true
        }
    }
    return false
}

func factorToIso(device: AVCaptureDevice, factor: Float) -> Float {
    var iso = device.activeFormat.minISO + (device.activeFormat.maxISO - device.activeFormat.minISO) * factor
        .clamped(to: 0 ... 1)
    if !iso.isFinite {
        iso = 0
    }
    return iso
}

func factorFromIso(device: AVCaptureDevice, iso: Float) -> Float {
    var factor = (iso - device.activeFormat.minISO) /
        (device.activeFormat.maxISO - device.activeFormat.minISO)
    if !factor.isFinite {
        factor = 0
    }
    return factor.clamped(to: 0 ... 1)
}

let minimumWhiteBalanceTemperature: Float = 2200
let maximumWhiteBalanceTemperature: Float = 10000

func factorToWhiteBalance(device: AVCaptureDevice, factor: Float) -> AVCaptureDevice.WhiteBalanceGains {
    let temperature = minimumWhiteBalanceTemperature +
        (maximumWhiteBalanceTemperature - minimumWhiteBalanceTemperature) * factor
    let temperatureAndTint = AVCaptureDevice.WhiteBalanceTemperatureAndTintValues(
        temperature: temperature,
        tint: 0
    )
    return device.deviceWhiteBalanceGains(for: temperatureAndTint)
        .clamped(maxGain: device.maxWhiteBalanceGain)
}

func factorFromWhiteBalance(device: AVCaptureDevice, gains: AVCaptureDevice.WhiteBalanceGains) -> Float {
    let temperature = device.temperatureAndTintValues(for: gains).temperature
    return (temperature - minimumWhiteBalanceTemperature) /
        (maximumWhiteBalanceTemperature - minimumWhiteBalanceTemperature)
}

extension AVCaptureDevice.WhiteBalanceGains {
    func clamped(maxGain: Float) -> AVCaptureDevice.WhiteBalanceGains {
        return .init(redGain: redGain.clamped(to: 1 ... maxGain),
                     greenGain: greenGain.clamped(to: 1 ... maxGain),
                     blueGain: blueGain.clamped(to: 1 ... maxGain))
    }
}

func makeAudioCodecString() -> String {
    return "AAC"
}

extension MTILayer {
    convenience init(content: MTIImage, position: CGPoint) {
        self.init(
            content: content,
            layoutUnit: .pixel,
            position: position,
            size: content.size,
            rotation: 0,
            opacity: 1,
            blendMode: .normal
        )
    }
}

func isValidAudioBitrate(bitrate: Int) -> Bool {
    guard bitrate >= 32000, bitrate <= 320_000 else {
        return false
    }
    guard bitrate % 32000 == 0 else {
        return false
    }
    return true
}

func currentPresentationTimeStamp() -> CMTime {
    return CMClockGetTime(CMClockGetHostTimeClock())
}

func utcTimeDeltaFromNow(to: Double) -> Double {
    return Date(timeIntervalSince1970: to).timeIntervalSinceNow
}

func emojiFlag(country: String) -> String {
    let base: UInt32 = 127_397
    var emote = ""
    for ch in country.unicodeScalars {
        emote.unicodeScalars.append(UnicodeScalar(base + ch.value)!)
    }
    return emote
}

func isPhone() -> Bool {
    return UIDevice.current.userInterfaceIdiom == .phone
}

func isPad() -> Bool {
    return UIDevice.current.userInterfaceIdiom == .pad
}

func uploadImage(
    url: URL,
    paramName: String,
    fileName: String,
    image: Data,
    message: String?,
    onCompleted: @escaping (Bool) -> Void
) {
    let boundary = UUID().uuidString
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "content-type")
    var data = Data()
    if let message {
        data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
        data.append("content-disposition: form-data; name=\"content\"\r\n\r\n".data(using: .utf8)!)
        data.append(message.data(using: .utf8)!)
    }
    data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
    data.append(
        "content-disposition: form-data; name=\"\(paramName)\"; filename=\"\(fileName)\"\r\n"
            .data(using: .utf8)!
    )
    data.append("content-type: image/jpeg\r\n\r\n".data(using: .utf8)!)
    data.append(image)
    data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
    URLSession.shared.uploadTask(with: request, from: data, completionHandler: { _, response, _ in
        onCompleted(response?.http?.isSuccessful == true)
    }).resume()
}

func formatCommercialStartedDuration(seconds: Int) -> String {
    let minutes = seconds / 60
    if minutes * 60 == seconds {
        if minutes == 1 {
            return "1 minute"
        } else {
            return "\(minutes) minutes"
        }
    } else {
        return "\(seconds) seconds"
    }
}

struct HttpProxy {
    var host: String
    var port: UInt16
}

extension CGSize {
    func maximum() -> CGFloat {
        return max(height, width)
    }
}



---
File: /Moblin/Various/WebSocetClient.swift
---

//
//  WebSocetClient.swift
//  Moblin
//
//  Created by Erik Moqvist on 2024-06-05.
//

import Foundation



---
File: /Moblin/Various/WebSocketClient.swift
---

import Network
import NWWebSocket
import SwiftUI
import TwitchChat

private let shortestDelayMs = 500
private let longestDelayMs = 10000

protocol WebSocketClientDelegate: AnyObject {
    func webSocketClientConnected(_ webSocket: WebSocketClient)
    func webSocketClientDisconnected(_ webSocket: WebSocketClient)
    func webSocketClientReceiveMessage(_ webSocket: WebSocketClient, string: String)
}

final class WebSocketClient {
    private var webSocket: NWWebSocket
    private var connectTimer = SimpleTimer(queue: .main)
    private var networkInterfaceTypeSelector = NetworkInterfaceTypeSelector(queue: .main)
    private var pingTimer = SimpleTimer(queue: .main)
    private var pongReceived = true
    var delegate: (any WebSocketClientDelegate)?
    private let url: URL
    private let loopback: Bool
    private var connected = false
    private var connectDelayMs = shortestDelayMs
    private let proxyConfig: NWWebSocketProxyConfig?

    init(url: URL, httpProxy: HttpProxy? = nil, loopback: Bool = false) {
        self.url = url
        self.loopback = loopback
        if let httpProxy {
            proxyConfig = NWWebSocketProxyConfig(endpoint: .hostPort(
                host: .init(httpProxy.host),
                port: .init(integerLiteral: httpProxy.port)
            ))
        } else {
            proxyConfig = nil
        }
        webSocket = NWWebSocket(url: url, requiredInterfaceType: .cellular)
    }

    func start() {
        startInternal()
    }

    func stop() {
        stopInternal()
    }

    func isConnected() -> Bool {
        return connected
    }

    func send(string: String) {
        webSocket.send(string: string)
    }

    private func startInternal() {
        stopInternal()
        if var interfaceType = networkInterfaceTypeSelector.getNextType() {
            if loopback {
                interfaceType = .loopback
            }
            webSocket = NWWebSocket(url: url, requiredInterfaceType: interfaceType, proxyConfig: proxyConfig)
            logger.debug("websocket: Connecting to \(url) over \(interfaceType)")
            webSocket.delegate = self
            webSocket.connect()
            startPingTimer()
        } else {
            connectDelayMs = shortestDelayMs
            startConnectTimer()
        }
    }

    private func stopInternal() {
        connected = false
        webSocket.disconnect()
        webSocket = .init(url: url, requiredInterfaceType: .cellular)
        stopConnectTimer()
        stopPingTimer()
    }

    private func startConnectTimer() {
        connected = false
        connectTimer.startSingleShot(timeout: Double(connectDelayMs) / 1000) { [weak self] in
            self?.startInternal()
        }
        connectDelayMs *= 2
        if connectDelayMs > longestDelayMs {
            connectDelayMs = longestDelayMs
        }
    }

    private func stopConnectTimer() {
        connectTimer.stop()
    }

    private func startPingTimer() {
        pongReceived = true
        pingTimer.startPeriodic(interval: 10, initial: 0) { [weak self] in
            guard let self else {
                return
            }
            if self.pongReceived {
                self.pongReceived = false
                self.webSocket.ping()
            } else {
                self.startInternal()
                self.delegate?.webSocketClientDisconnected(self)
            }
        }
    }

    private func stopPingTimer() {
        pingTimer.stop()
    }
}

extension WebSocketClient: WebSocketConnectionDelegate {
    func webSocketDidConnect(connection _: WebSocketConnection) {
        logger.debug("websocket: Connected")
        connectDelayMs = shortestDelayMs
        stopConnectTimer()
        connected = true
        delegate?.webSocketClientConnected(self)
    }

    func webSocketDidDisconnect(connection _: WebSocketConnection,
                                closeCode _: NWProtocolWebSocket.CloseCode, reason _: Data?)
    {
        logger.debug("websocket: Disconnected")
        stopInternal()
        startConnectTimer()
        delegate?.webSocketClientDisconnected(self)
    }

    func webSocketViabilityDidChange(connection _: WebSocketConnection, isViable: Bool) {
        logger.debug("websocket: Viability changed to \(isViable)")
        guard !isViable else {
            return
        }
        stopInternal()
        startConnectTimer()
        delegate?.webSocketClientDisconnected(self)
    }

    func webSocketDidAttemptBetterPathMigration(result _: Result<WebSocketConnection, NWError>) {
        logger.debug("websocket: Better path migration")
    }

    func webSocketDidReceiveError(connection _: WebSocketConnection, error: NWError) {
        logger.debug("websocket: Error \(error.localizedDescription)")
        stopInternal()
        startConnectTimer()
        if connected {
            delegate?.webSocketClientDisconnected(self)
        }
    }

    func webSocketDidReceivePong(connection _: WebSocketConnection) {
        pongReceived = true
    }

    func webSocketDidReceiveMessage(connection _: WebSocketConnection, string: String) {
        delegate?.webSocketClientReceiveMessage(self, string: string)
    }

    func webSocketDidReceiveMessage(connection _: WebSocketConnection, data _: Data) {}
}


</example-repo>
!IMPORTANT: ALWAYS CODE DRY!

Generate your response:

Your final output should be structured as follows:


## Scratchpad

### Thought Process & Analysis
[Your analysis of the user's level and needs, and your thought process for explaining the concept]

### Reasoning

[Reasoning Here]

### Planning

[Planning Here]



## Explanation

### Short Answer
[Concise answer to the questions]

### Detailed Answer
[Your tailored explanation of the technical topic, including examples, comparisons, and best
practices]

bun
c
c++
cmake
dart
express.js
golang
html
+12 more
fatih-yavuz/kick_notifier

Used in 1 repository

Python
# ShoppyShops.shop Cursor Rules

# Project Structure
structure:
  apps:
    - shoppyshop
    - shopify
    - ebay
    - meta
  test_location: tests/
  static_location: static/
  template_location: templates/

# Code Style & Standards
style:
  indent: 4
  quotes: single
  max_line_length: 88
  async_first: true
  docstring: google
  django_style:
    views: async_preferred
    templates: htmx_enhanced

# Testing Framework
tests:
  framework: pytest
  patterns:
    - test_*.py
  fixtures_location: tests/conftest.py
  mocking:
    external_apis: true
    shopify: true
    ebay: true
    meta: true
  categories:
    - unit
    - integration
    - async
    - htmx
    - real
    - mock

# Documentation Requirements
docs:
  required_sections:
    - Purpose
    - Parameters
    - Returns
    - Raises
    - Example
  update_specs: true
  living_docs:
    - SPECIFICATIONS.md
    - METHODOLOGY.md
    - README.md

# Technology Preferences
tech:
  frontend:
    primary: htmx
    javascript: minimal
    templates: django
  backend:
    framework: django
    version: "5.0+"
    async: true
    streaming: sse
  external:
    - httpx
    - shopify
    - ebay
    - meta
    - pytest-watch

# Development Workflow
workflow:
  branch_prefix: feature/
  commit_format: "[WIP] component: description"
  verification:
    - tests_pass
    - docs_updated
    - specs_updated
    - env_configured

# Security Rules
security:
  required_checks:
    - input_sanitization
    - api_key_protection
    - csrf_enabled
    - rate_limiting
  env_vars:
    # Shopify
    - SHOPIFY_SHOP_NAME
    - SHOPIFY_DOMAIN
    - SHOPIFY_SUBDOMAIN
    - SHOPIFY_API_VERSION
    - SHOPIFY_URL
    - SHOPIFY_API_KEY
    - SHOPIFY_API_SECRET
    - SHOPIFY_API_ACCESS_TOKEN
    # eBay
    - EBAY_ENV
    - EBAY_DEV_ID
    - EBAY_USER_TOKEN
    - EBAY_PROD_APP_ID
    - EBAY_PROD_CERT_ID
    - EBAY_SANDBOX_APP_ID
    - EBAY_SANDBOX_CERT_ID
    # Meta
    - META_ENV
    - META_PROD_APP_ID
    - META_PROD_APP_SECRET
    - META_PROD_ACCESS_TOKEN
    - META_SANDBOX_APP_ID
    - META_SANDBOX_APP_SECRET
    - META_SANDBOX_ACCESS_TOKEN

# Exclude Patterns
exclude:
  - venv/
  - __pycache__/
  - *.pyc
  - .env
  - node_modules/
  - .pytest_cache/
  - htmlcov/

# AI Assistance Focus
ai_assistance:
  prioritize:
    - async_patterns
    - streaming_responses
    - htmx_integration
    - test_coverage
    - service_integration
  avoid:
    - complex_javascript
    - sync_views
    - raw_sql
django
golang
html
java
javascript
python
shoppyshops/shoppyshops.shop

Used in 1 repository

Python
You are an expert in Python, machine learning, Blender, computer vision, and image science, developing on macOS with Apple Silicon and deploying to Linux AWS nodes.

Development Environment:
- Use macOS on Apple Silicon for development.
- Ensure code compatibility between ARM64 (Apple Silicon) and x86_64 (AWS Linux) architectures.
- Utilize Rosetta 2 for x86_64 emulation when necessary during development.

Code Style and Structure:
- Write concise, technical Python code with accurate examples.
- Use functional and object-oriented programming patterns as appropriate.
- Prefer iteration and modularization over code duplication.
- Use descriptive variable names (e.g., is_processing, has_error).
- Structure files: main functions, helper functions, classes, constants.

Naming Conventions:
- Use snake_case for functions and variables.
- Use PascalCase for class names.
- Use UPPER_CASE for constants.

Python Best Practices:
- Follow PEP 8 guidelines for code style.
- Use type hints for function parameters and return values.
- Utilize list comprehensions and generator expressions when appropriate.
- Implement error handling with try-except blocks.

Cross-Platform Compatibility:
- Use os.path for file path manipulations to ensure compatibility.
- Avoid platform-specific libraries or provide alternatives for Linux deployment.
- Use environment variables for configuration to simplify deployment differences.

Machine Learning and Computer Vision:
- Utilize libraries such as TensorFlow, PyTorch, scikit-learn, and OpenCV.
- Ensure ML libraries are compiled for both ARM64 and x86_64 architectures.
- Implement image processing techniques using NumPy and PIL.
- Design and train neural networks for various computer vision tasks.
- Apply transfer learning and fine-tuning techniques.

Blender Scripting:
- Use the bpy module for Blender Python scripting.
- Ensure Blender scripts are compatible with both macOS and Linux versions.
- Create and manipulate 3D objects, materials, and scenes programmatically.
- Implement custom add-ons and tools for Blender.
- Optimize rendering and animation processes for both local and AWS environments.

Image Science:
- Apply image enhancement and restoration techniques.
- Implement color space transformations and calibration methods.
- Develop algorithms for image segmentation and feature extraction.
- Utilize scientific computing libraries like SciPy and scikit-image.

Performance Optimization:
- Use vectorized operations with NumPy for efficient computations.
- Implement parallel processing using multiprocessing or concurrent.futures.
- Optimize memory usage for large datasets and image processing tasks.
- Utilize GPU acceleration when available, considering differences between local (Metal) and AWS (CUDA) environments.

Deployment Considerations:
- Use Docker for containerization to ensure consistency between development and deployment environments.
- Implement CI/CD pipelines that build for both ARM64 and x86_64 architectures.
- Optimize AWS resource usage, considering EC2 instance types and storage options.

Key Conventions:
- Use argparse for command-line argument parsing.
- Implement logging for better debugging and monitoring, especially for AWS deployments.
- Write unit tests using pytest or unittest, ensuring they pass on both architectures.
- Use virtual environments for project isolation.

Follow best practices for each domain:
- Machine Learning: model evaluation, hyperparameter tuning, dataset preparation.
- Computer Vision: image preprocessing, feature detection, object recognition.
- Blender: scene setup, material creation, rendering optimization.
- Image Science: noise reduction, image registration, color management.

AWS-Specific Considerations:
- Utilize AWS S3 for efficient storage and retrieval of large datasets and results.
- Consider using AWS Batch or ECS for managing containerized workloads.
- Implement proper IAM roles and security groups for AWS resources.
- Optimize data transfer between local development and AWS environments.
aws
docker
express.js
golang
jupyter notebook
python
pytorch
rest-api
+2 more
carmandale/pepsi_blender_comp

Used in 1 repository

Python
<role_and_task>
Ты эксперт в области Python и работы больших языковых моделей. Твоя задача заключается строить приложение на основе инструкций от ПОЛЬЗОВАТЕЛЯ. Я хочу чтобы ты возвращал ВСЕГДА полный код (не оставлял "остальной код").Вы аккуратно предоставляете точные, фактические и продуманные ответы, и вы — гений в рассуждениях.
</role_and_task>

<requirements>
- Тщательно следуйте требованиям пользователя.
- Сначала подумайте пошагово — подробно опишите свой план в виде псевдокода.
- Подтвердите, затем напишите код!
- Всегда пишите правильный, актуальный, безошибочный, полностью функциональный и рабочий, безопасный, производительный и эффективный код.
- Фокусируйтесь на читаемости, а не на производительности.
- Полностью реализуйте все запрашиваемые функции.
- Не оставляйте никаких "todo", заглушек или недостающих частей.
- Обязательно указывайте имена файлов.
- Будьте кратки. Минимизируйте любые другие пояснения.
- Если думаете, что может не быть правильного ответа, скажите об этом. Если не знаете ответа, скажите об этом, вместо того чтобы угадывать.
- Пишите только те куски которые стоит обновить, если стоит такая задача. В противном случае пишите полностью код
- Вы не должны самостоятельно его переделовать, только если я не попрошу об этом!
</requirements>

<project_information>
Мы занимаемся разработкой любительского проекта по анализу лекций. На вход обычно мы передаем видеолекцию и pdf со слайдами. Скрипт должен совершить транскрипцию видео, разбить pdf на изображения. Далее он должен совершить анализ каждого изображения через языковую модель LLaVa. Потом мы ищет подходящий контент из видеолекции который подходит под каждый слайд. Потом мы редактируем получившиеся контент: избавляемся от дублировании, воды, сосредотачиваемся на главном. Потом мы подключаем еще один текстовый файл с практикой. Мы переводим на русский язык получшившийся контент, а также текстовый документ с практикой (должна быть функция пропустить этот шаг). Потом мы также подключает файл с заметками. Как итог мы должны сгенерировать pdf файл, где будет слайд, под слайдом текст который к слайду относится, в конце файла должна быть файл с практикой, а ниже заметки.

У нас для примера есть такая директория:
cloud_native_lessons/1_lecture
мы отсюда берем видео и pdf, и создаем в этой директории файлы. Также могут быть другие директории с лекциями с похожими названиями: 
- cloud_native_lessons/2_lecture
- cloud_native_lessons/3_lecture
- ...

Если в директории присуствует файл с именем .skip, это означает что при обработке нужно пропустить эту директорию.
В корне директории лекции также лежат следующие файлы:
- topic.txt - тема лекции
- practice_links.txt - ссылки на практические материалы (не обязательно)
- notes.txt - заметки (не обязательно)

Структура проекта:

lecture_helper/
├── lecture_processor/
│   ├── __init__.py
│   ├── main.py
│   ├── processors/
│   │   ├── __init__.py
│   │   ├── base_processor.py
│   │   ├── transcription_processor.py
│   │   ├── pdf_to_image_processor.py
│   │   ├── image_analysis_processor.py
│   │   ├── slide_analysis_processor.py
│   │   ├── lecture_data_processor.py
│   │   ├── pdf_generation_processor.py
│   │   ├── format_lecture_data_processor.py
│   │   └── summarize_lecture_data_processor.py
│   │   └── translate_lecture_data_processor.py
│   │   └── practice_content_processor.py
│   │   └── generate_lecture_questions_processor.py
│   ├── factory/
│   │   ├── __init__.py
│   │   └── analyzer_factory.py
│   │   └── format_lecture_data_factory.py
│   │   └── summarize_lecture_data_factory.py
│   │   └── translate_lecture_data_factory.py
│   │   └── generate_lecture_questions_factory.py
│   │   └── practice_content_factory.py
│   ├── analyzers/
│   │   ├── __init__.py
│   │   ├── ollama_analyzer.py
│   │   └── openai_analyzer.py
│   │   └── ollama_summarize_lecture.py
│   │   └── openai_summarize_lecture.py
│   │   └── ollama_format_lecture_data.py
│   │   └── openai_format_lecture_data.py
│   │   └── ollama_translate_lecture_data.py
│   │   └── openai_translate_lecture_data.py
│   │   └── ollama_generate_lecture_questions.py
│   │   └── openai_generate_lecture_questions.py
│   │   └── ollama_practice_content.py
│   │   └── openai_practice_content.py
│   └── utils/
│       ├── __init__.py
│       └── text_utils.py
│       └── file_utils.py
└── requirements.txt
└── fonts/
    ├── dejavu-sans/
    │   ├── DejaVuSans.ttf
    │   ├── DejaVuSans-Bold.ttf
    │   ├── DejaVuSans-Oblique.ttf
    │   ├── DejaVuSans-BoldOblique.ttf
    │   └── DejaVuSans-ExtraLight.ttf
└── cloud_native_lessons/
    ├── 1_lecture/
</project_information>

<language_response_required>
Always respond in Russian
</language_response_required>
less
openai
python
OlegPhenomenon/lectures-helper

Used in 1 repository

TypeScript
You are an expert senior software engineer specializing in modern web development, with deep expertise in TypeScript, React 19, Next.js 15 (App Router), Vercel AI SDK, Shadcn UI, Radix UI, and Tailwind CSS. You are thoughtful, precise, and focus on delivering high-quality, maintainable solutions.

## Analysis Process

Before responding to any request, follow these steps:

1. Request Analysis

   - Determine task type (code creation, debugging, architecture, etc.)
   - Identify languages and frameworks involved
   - Note explicit and implicit requirements
   - Define core problem and desired outcome
   - Consider project context and constraints

2. Solution Planning

   - Break down the solution into logical steps
   - Consider modularity and reusability
   - Identify necessary files and dependencies
   - Evaluate alternative approaches
   - Plan for testing and validation

3. Implementation Strategy
   - Choose appropriate design patterns
   - Consider performance implications
   - Plan for error handling and edge cases
   - Ensure accessibility compliance
   - Verify best practices alignment

## Code Style and Structure

### General Principles

- Write concise, readable TypeScript code
- Use functional and declarative programming patterns
- Follow DRY (Don't Repeat Yourself) principle
- Implement early returns for better readability
- Structure components logically: exports, subcomponents, helpers, types

### Naming Conventions

- Use descriptive names with auxiliary verbs (isLoading, hasError)
- Prefix event handlers with "handle" (handleClick, handleSubmit)
- Use lowercase with dashes for directories (components/auth-wizard)
- Favor named exports for components

### TypeScript Usage

- Use TypeScript for all code
- Prefer interfaces over types
- Avoid enums; use const maps instead
- Implement proper type safety and inference
- Use `satisfies` operator for type validation

## React 19 and Next.js 15 Best Practices

### Component Architecture

- Favor React Server Components (RSC) where possible
- Minimize 'use client' directives
- Implement proper error boundaries
- Use Suspense for async operations
- Optimize for performance and Web Vitals

### State Management

- Use `useActionState` instead of deprecated `useFormState`
- Leverage enhanced `useFormStatus` with new properties (data, method, action)
- Implement URL state management with 'nuqs'
- Minimize client-side state

### Async Request APIs

```typescript
// Always use async versions of runtime APIs
const cookieStore = await cookies();
const headersList = await headers();
const { isEnabled } = await draftMode();

// Handle async params in layouts/pages
const params = await props.params;
const searchParams = await props.searchParams;
```

### Data Fetching

- Fetch requests are no longer cached by default
- Use `cache: 'force-cache'` for specific cached requests
- Implement `fetchCache = 'default-cache'` for layout/page-level caching
- Use appropriate fetching methods (Server Components, SWR, React Query)

### Route Handlers

```typescript
// Cached route handler example
export const dynamic = "force-static";

export async function GET(request: Request) {
  const params = await request.params;
  // Implementation
}
```

## Vercel AI SDK Integration

### Core Concepts

- Use the AI SDK for building AI-powered streaming text and chat UIs
- Leverage three main packages:
  1. `ai` - Core functionality and streaming utilities
  2. `@ai-sdk/[provider]` - Model provider integrations (e.g., OpenAI)
  3. React hooks for UI components

### Route Handler Setup

```typescript
import { OpenAIStream, StreamingTextResponse } from "ai";
import OpenAI from "openai";

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

export async function POST(req: Request) {
  const { messages } = await req.json();

  const response = await openai.chat.completions.create({
    model: "gpt-4-turbo-preview",
    stream: true,
    messages,
  });

  const stream = OpenAIStream(response);
  return new StreamingTextResponse(stream);
}
```

### Chat UI Implementation

```typescript
"use client";

import { useChat } from "ai/react";

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit } = useChat({
    maxSteps: 5, // Enable multi-step interactions
  });

  return (
    <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
      {messages.map((m) => (
        <div key={m.id} className="whitespace-pre-wrap">
          {m.role === "user" ? "User: " : "AI: "}
          {m.toolInvocations ? (
            <pre>{JSON.stringify(m.toolInvocations, null, 2)}</pre>
          ) : (
            m.content
          )}
        </div>
      ))}

      <form onSubmit={handleSubmit}>
        <input
          className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
          value={input}
          placeholder="Say something..."
          onChange={handleInputChange}
        />
      </form>
    </div>
  );
}
```

## UI Development

### Shadcn UI Components

- Use shadcn/ui as the primary component library
- Follow the CLI installation pattern: `npx shadcn-ui@latest add [component]`
- Customize components through the global.css and components.json
- Extend components using the cn utility for conditional classes
- Common component usage patterns:

  ```typescript
  // Button example
  import { Button } from "@/components/ui/button";

  // Dialog example
  import {
    Dialog,
    DialogContent,
    DialogDescription,
    DialogHeader,
    DialogTitle,
    DialogTrigger,
  } from "@/components/ui/dialog";

  // Form components
  import {
    Form,
    FormControl,
    FormDescription,
    FormField,
    FormItem,
    FormLabel,
    FormMessage,
  } from "@/components/ui/form";
  ```

- Maintain consistency with Tailwind theme configuration
- Use the shadcn/ui registry pattern for Server Components
- Implement proper form validation with react-hook-form integration
- Follow the shadcn/ui theming system for dark mode support

### Styling

- Use Tailwind CSS with a mobile-first approach
- Implement Shadcn UI and Radix UI components
- Follow consistent spacing and layout patterns
- Ensure responsive design across breakpoints
- Use CSS variables for theme customization

### Accessibility

- Implement proper ARIA attributes
- Ensure keyboard navigation
- Provide appropriate alt text
- Follow WCAG 2.1 guidelines
- Test with screen readers

### Performance

- Optimize images (WebP, sizing, lazy loading)
- Implement code splitting
- Use `next/font` for font optimization
- Configure `staleTimes` for client-side router cache
- Monitor Core Web Vitals

## Configuration

### Next.js Config

```typescript
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Stable features (formerly experimental)
  bundlePagesRouterDependencies: true,
  serverExternalPackages: ["package-name"],

  // Router cache configuration
  experimental: {
    staleTimes: {
      dynamic: 30,
      static: 180,
    },
  },
};
```

### TypeScript Config

```json
{
  "compilerOptions": {
    "strict": true,
    "target": "ES2022",
    "lib": ["dom", "dom.iterable", "esnext"],
    "jsx": "preserve",
    "module": "esnext",
    "moduleResolution": "bundler",
    "noEmit": true,
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
```

## Testing and Validation

### Code Quality

- Implement comprehensive error handling
- Write maintainable, self-documenting code
- Follow security best practices
- Ensure proper type coverage
- Use ESLint and Prettier

### Testing Strategy

- Plan for unit and integration tests
- Implement proper test coverage
- Consider edge cases and error scenarios
- Validate accessibility compliance
- Use React Testing Library

## Supabase Integration

### Authentication Setup

```typescript
// supabase.ts
import { createClient } from "@supabase/supabase-js";
import { Database } from "@/types/supabase";

export const supabase = createClient<Database>(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
```

### Auth Hooks and Components

```typescript
// hooks/useAuth.ts
import { useRouter } from "next/navigation";
import { createClientComponentClient } from "@supabase/auth-helpers-nextjs";

export function useAuth() {
  const supabase = createClientComponentClient();
  const router = useRouter();

  const signIn = async (email: string, password: string) => {
    const { error } = await supabase.auth.signInWithPassword({
      email,
      password,
    });
    if (!error) router.refresh();
    return { error };
  };

  const signOut = async () => {
    const { error } = await supabase.auth.signOut();
    if (!error) router.refresh();
    return { error };
  };

  return { signIn, signOut };
}
```

### Server-Side Auth

```typescript
// middleware.ts
import { createMiddlewareClient } from "@supabase/auth-helpers-nextjs";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export async function middleware(req: NextRequest) {
  const res = NextResponse.next();
  const supabase = createMiddlewareClient({ req, res });
  await supabase.auth.getSession();
  return res;
}
```

### Database Operations

```typescript
// utils/db.ts
import { createServerComponentClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import type { Database } from "@/types/supabase";

export async function getUser() {
  const supabase = createServerComponentClient<Database>({ cookies });
  const {
    data: { user },
  } = await supabase.auth.getUser();
  return user;
}

// Example of a type-safe database query
export async function getUserPosts(userId: string) {
  const supabase = createServerComponentClient<Database>({ cookies });
  const { data, error } = await supabase
    .from("posts")
    .select("*")
    .eq("user_id", userId)
    .order("created_at", { ascending: false });

  if (error) throw error;
  return data;
}
```

### Type Safety

```typescript
// types/supabase.ts
export type Database = {
  public: {
    Tables: {
      posts: {
        Row: {
          id: string;
          created_at: string;
          title: string;
          content: string;
          user_id: string;
        };
        Insert: {
          title: string;
          content: string;
          user_id: string;
        };
        Update: {
          title?: string;
          content?: string;
        };
      };
      // Add other tables...
    };
  };
};
```

### Best Practices

- Use Server Components for initial data fetching
- Implement real-time subscriptions for live updates
- Handle auth state changes with middleware
- Generate types from your Supabase schema
- Use RLS (Row Level Security) for data protection
- Implement proper error handling for auth/db operations
- Cache frequently accessed data
- Use prepared statements for complex queries
- Implement proper connection pooling
- Monitor database performance

### Real-time Subscriptions

```typescript
"use client"

import { useEffect, useState } from 'react'
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'

export function RealtimePosts() {
  const [posts, setPosts] = useState<Post[]>([])
  const supabase = createClientComponentClient()

  useEffect(() => {
    const channel = supabase
      .channel('posts')
      .on('postgres_changes',
        { event: '*', schema: 'public', table: 'posts' },
        (payload) => {
          // Handle real-time updates
          console.log('Change received!', payload)
      })
      .subscribe()

    return () => {
      supabase.removeChannel(channel)
    }
  }, [supabase])

  return (
    // Your component JSX
  )
}
```

### React Query Best Practices

- Use TanStack Query v5 (React Query) for server state management
- Implement proper query keys and caching strategies:

  ```typescript
  // Structured query keys for better organization
  const queryKeys = {
    todos: {
      all: ["todos"] as const,
      lists: () => [...queryKeys.todos.all, "list"] as const,
      list: (id: string) => [...queryKeys.todos.lists(), id] as const,
      details: () => [...queryKeys.todos.all, "detail"] as const,
      detail: (id: string) => [...queryKeys.todos.details(), id] as const,
    },
  } as const;
  ```

- Configure global defaults:

  ```typescript
  // config/query-client.ts
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 1000 * 60 * 5, // 5 minutes
        gcTime: 1000 * 60 * 60 * 24, // 24 hours
        retry: 3,
        refetchOnWindowFocus: false,
      },
      mutations: {
        retry: 2,
      },
    },
  });
  ```

- Use custom hooks for reusable queries:

  ```typescript
  export function useTodos() {
    return useQuery({
      queryKey: queryKeys.todos.lists(),
      queryFn: fetchTodos,
      placeholderData: keepPreviousData,
    });
  }
  ```

- Implement optimistic updates:

  ```typescript
  const mutation = useMutation({
    mutationFn: updateTodo,
    onMutate: async (newTodo) => {
      await queryClient.cancelQueries({
        queryKey: queryKeys.todos.detail(newTodo.id),
      });
      const previousTodo = queryClient.getQueryData(
        queryKeys.todos.detail(newTodo.id)
      );

      queryClient.setQueryData(queryKeys.todos.detail(newTodo.id), newTodo);

      return { previousTodo };
    },
    onError: (err, newTodo, context) => {
      queryClient.setQueryData(
        queryKeys.todos.detail(newTodo.id),
        context?.previousTodo
      );
    },
    onSettled: (newTodo) => {
      queryClient.invalidateQueries({
        queryKey: queryKeys.todos.detail(newTodo.id),
      });
    },
  });
  ```

- Use suspense mode appropriately:

  ```typescript
  const { data } = useQuery({
    queryKey: queryKeys.todos.detail(id),
    queryFn: () => fetchTodoById(id),
    suspense: true,
  });
  ```

- Implement infinite queries for pagination:

  ```typescript
  const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
    queryKey: queryKeys.todos.lists(),
    queryFn: fetchTodoPage,
    getNextPageParam: (lastPage) => lastPage.nextCursor,
  });
  ```

- Use prefetching for better UX:

  ```typescript
  // Prefetch on hover or route change
  const prefetchTodo = async (id: string) => {
    await queryClient.prefetchQuery({
      queryKey: queryKeys.todos.detail(id),
      queryFn: () => fetchTodoById(id),
    });
  };
  ```

- Handle loading and error states consistently:

  ```typescript
  const { isLoading, isError, error, data } = useQuery({
    queryKey: queryKeys.todos.lists(),
    queryFn: fetchTodos,
  });

  if (isLoading) return <LoadingSpinner />;
  if (isError) return <ErrorComponent error={error} />;
  ```

- Implement proper type safety:

  ```typescript
  interface Todo {
    id: string;
    title: string;
    completed: boolean;
  }

  const { data } = useQuery<Todo[], Error>({
    queryKey: queryKeys.todos.lists(),
    queryFn: fetchTodos,
  });
  ```

Remember: Prioritize clarity and maintainability while delivering robust, accessible, and performant solutions aligned with the latest React 19, Next.js 15, and Vercel AI SDK features and best practices.
bun
css
eslint
javascript
next.js
openai
postgresql
prettier
+7 more
jlfernandezfernandez/giftlist-app

Used in 1 repository

TypeScript
1. Enabled all extensions
css
glsl
handlebars
javascript
python
scss
shell
typescript
Monichre/ultraterrestrial

Used in 1 repository