Compare commits
10 Commits
1745807625
...
e080aba000
| Author | SHA1 | Date |
|---|---|---|
|
|
e080aba000 | |
|
|
ec9f997c73 | |
|
|
dd9b242876 | |
|
|
c2adacf7d8 | |
|
|
bf7c0fde39 | |
|
|
1c399b1b1c | |
|
|
3b3c59d49b | |
|
|
8976ef07c7 | |
|
|
4ac3ab5e5b | |
|
|
f6ff47e47b |
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(cp:*)",
|
||||||
|
"Bash(python3:*)",
|
||||||
|
"Bash(pip install:*)",
|
||||||
|
"Bash(mkdir:*)",
|
||||||
|
"Bash(git add:*)"
|
||||||
|
],
|
||||||
|
"deny": [],
|
||||||
|
"ask": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Ignore log files
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# Ignore git
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Ignore Docker files
|
||||||
|
Dockerfile
|
||||||
|
docker-compose.yml
|
||||||
|
.dockerignore
|
||||||
|
|
||||||
|
# Ignore Python cache
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.Python
|
||||||
|
|
||||||
|
# Ignore IDE files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Ignore OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This is a Python automation tool that integrates UniFi Access with Airbnb/Hostex reservations, automatically creating and managing visitor access for Airbnb guests. The system fetches reservations from either Hostex API or Airbnb ICS feed, creates UniFi Access visitor accounts with PIN codes based on phone numbers, and provides cross-verification between multiple booking systems.
|
||||||
|
|
||||||
|
## Core Commands
|
||||||
|
|
||||||
|
### Development and Execution
|
||||||
|
- **Run the application**: `python3 main.py`
|
||||||
|
- **Install dependencies**: `pip install -r requirements.txt`
|
||||||
|
- **Run with verbose logging**: `python3 main.py -v`
|
||||||
|
- **Specify custom log file**: `python3 main.py -l custom.log`
|
||||||
|
- **List available door groups**: `python3 main.py --list-door-groups`
|
||||||
|
|
||||||
|
### Configuration Setup
|
||||||
|
- **Setup configuration**: `cp unifi.conf.example unifi.conf` then edit with actual credentials
|
||||||
|
- **Configuration file**: `unifi.conf` (contains API tokens, URLs, and settings)
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
### Core Components
|
||||||
|
|
||||||
|
1. **main.py** - Entry point and orchestration
|
||||||
|
- Handles command-line arguments and logging setup
|
||||||
|
- Coordinates all managers and performs cross-system verification
|
||||||
|
- Implements `verify_across_systems()` for data consistency checks
|
||||||
|
|
||||||
|
2. **unifi_access.py** - UniFi Access API integration
|
||||||
|
- `UnifiAccessManager` class handles all UniFi Access operations
|
||||||
|
- Creates/deletes visitors, assigns PIN codes, manages door group access
|
||||||
|
- Processes reservations and maintains visitor lifecycle
|
||||||
|
|
||||||
|
3. **hostex_api.py** - Hostex API integration
|
||||||
|
- `HostexManager` fetches reservations from Hostex booking platform
|
||||||
|
- Simple API wrapper for reservation data
|
||||||
|
|
||||||
|
4. **ics_parser.py** - Airbnb ICS calendar parsing
|
||||||
|
- `ICSParser` processes Airbnb calendar feeds
|
||||||
|
- Extracts reservation data and PIN codes from ICS events
|
||||||
|
|
||||||
|
5. **notification.py** - Simplepush notifications
|
||||||
|
- `NotificationManager` sends status updates and alerts
|
||||||
|
- Configurable notification system for operational updates
|
||||||
|
|
||||||
|
6. **config.py** - Configuration management
|
||||||
|
- Centralizes configuration loading from `unifi.conf`
|
||||||
|
- Handles API credentials, timing settings, and feature flags
|
||||||
|
|
||||||
|
7. **utils.py** - Shared utilities
|
||||||
|
- Logging setup and configuration
|
||||||
|
|
||||||
|
### Data Flow Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
Hostex API ──┐
|
||||||
|
├──► Cross-Verification ──► UniFi Access API
|
||||||
|
ICS Feed ────┘ (Visitor Management)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Simplepush Notifications
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Integration Points
|
||||||
|
|
||||||
|
- **Reservation Sources**: Supports both Hostex API and Airbnb ICS feeds
|
||||||
|
- **PIN Code Generation**: Uses last N digits of guest phone numbers
|
||||||
|
- **Cross-Verification**: Matches reservations across systems to detect discrepancies
|
||||||
|
- **Visitor Lifecycle**: Automatically creates visitors for upcoming stays, deletes past/completed visits
|
||||||
|
- **Door Access**: Assigns visitors to configurable door groups in UniFi Access
|
||||||
|
|
||||||
|
## Configuration Structure
|
||||||
|
|
||||||
|
The `unifi.conf` file contains these key sections:
|
||||||
|
- `[UniFi]`: Access controller API endpoint and authentication token
|
||||||
|
- `[Hostex]`: Hostex API credentials (optional if using ICS only)
|
||||||
|
- `[Airbnb]`: ICS calendar feed URL (optional if using Hostex only)
|
||||||
|
- `[Door]`: Default door group ID for visitor access
|
||||||
|
- `[Visitor]`: Check-in/out times and PIN code settings
|
||||||
|
- `[Simplepush]`: Notification service configuration
|
||||||
|
- `[General]`: Logging and PIN code digit count
|
||||||
|
|
||||||
|
## Development Notes
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
- `requests`: HTTP API calls to UniFi Access and Hostex
|
||||||
|
- `icalendar`: Parsing Airbnb ICS calendar feeds
|
||||||
|
- `urllib3`: HTTP utilities (with SSL warning suppression)
|
||||||
|
- `configparser`: Configuration file parsing
|
||||||
|
|
||||||
|
### Logging Strategy
|
||||||
|
- Comprehensive debug logging available with `-v` flag
|
||||||
|
- File-based logging for operational history
|
||||||
|
- Different log levels for console vs file output
|
||||||
|
|
||||||
|
### API Integrations
|
||||||
|
- **UniFi Access**: RESTful API for visitor management, requires authentication token
|
||||||
|
- **Hostex**: Booking platform API for reservation data
|
||||||
|
- **Simplepush**: Simple HTTP-based notification service
|
||||||
|
- **Airbnb ICS**: Calendar feed parsing for reservation extraction
|
||||||
|
|
||||||
|
### Testing and Verification
|
||||||
|
- Use `--list-door-groups` to verify UniFi Access connectivity and available door groups
|
||||||
|
- Run with `-v` flag to see detailed operation logs
|
||||||
|
- Check log files for historical operation data and error analysis
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Use Python 3.11 slim image (supports amd64, arm64, arm/v7)
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy requirements and install dependencies
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY *.py ./
|
||||||
|
COPY unifi.conf ./
|
||||||
|
|
||||||
|
# Create logs directory
|
||||||
|
RUN mkdir -p /app/logs
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
# Default command (can be overridden)
|
||||||
|
CMD ["python", "main.py"]
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
# Docker Setup for UniFi Access Airbnb Integration
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
1. **Build and run continuously** (checks every 6 hours):
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Run once manually**:
|
||||||
|
```bash
|
||||||
|
docker-compose --profile manual up unifi-access-airbnb-once
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **View logs**:
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f
|
||||||
|
# Or check the logs directory: ./logs/unifi_access_airbnb.log
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
- **Config file**: Edit `unifi.conf` on the host - it's mounted read-only into the container
|
||||||
|
- **Logs**: Persistent in `./logs/` directory
|
||||||
|
- **Timezone**: Set in docker-compose.yml (default: America/New_York)
|
||||||
|
|
||||||
|
## Automation Options
|
||||||
|
|
||||||
|
### Option 1: Continuous Container (Recommended)
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
Runs every 6 hours automatically. Change interval by modifying `sleep 21600` in docker-compose.yml.
|
||||||
|
|
||||||
|
### Option 2: Cron Job + Manual Container
|
||||||
|
```bash
|
||||||
|
# Add to crontab to run daily at 8 AM:
|
||||||
|
0 8 * * * cd /path/to/project && docker-compose --profile manual up unifi-access-airbnb-once
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 3: Windows Task Scheduler
|
||||||
|
Create a scheduled task that runs:
|
||||||
|
```cmd
|
||||||
|
docker-compose --profile manual up unifi-access-airbnb-once
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
- **Stop**: `docker-compose down`
|
||||||
|
- **Rebuild**: `docker-compose build`
|
||||||
|
- **View container status**: `docker-compose ps`
|
||||||
|
- **Interactive shell**: `docker-compose exec unifi-access-airbnb sh`
|
||||||
|
|
||||||
|
## Raspberry Pi Deployment
|
||||||
|
|
||||||
|
### Compatibility
|
||||||
|
✅ **Raspberry Pi 4/5** (ARM64) - Full support
|
||||||
|
✅ **Raspberry Pi 3B+** (ARM32/ARMv7) - Full support
|
||||||
|
✅ **Raspberry Pi Zero 2 W** (ARM64) - Works but slower
|
||||||
|
|
||||||
|
### Pi-Specific Setup
|
||||||
|
```bash
|
||||||
|
# 1. Install Docker (if not already installed)
|
||||||
|
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||||
|
sudo sh get-docker.sh
|
||||||
|
sudo usermod -aG docker pi
|
||||||
|
|
||||||
|
# 2. Install docker-compose
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install docker-compose-plugin
|
||||||
|
|
||||||
|
# 3. Clone and run your project
|
||||||
|
git clone <your-repo>
|
||||||
|
cd unifi-access-airbnb
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Performance Notes
|
||||||
|
- **Pi 4/5**: Excellent performance, recommended for production
|
||||||
|
- **Pi 3B+**: Good performance, slightly slower container startup
|
||||||
|
- **Pi Zero 2**: Functional but slower, consider running less frequently
|
||||||
|
|
||||||
|
### Power Management
|
||||||
|
For reliable operation on Pi:
|
||||||
|
```yaml
|
||||||
|
# Add to docker-compose.yml services section:
|
||||||
|
restart: always
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 128M
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **Check logs**: `docker-compose logs unifi-access-airbnb`
|
||||||
|
- **Test config**: `docker-compose --profile manual up unifi-access-airbnb-once`
|
||||||
|
- **Network issues**: Ensure container can reach UniFi controller IP (10.0.1.20)
|
||||||
|
- **Pi-specific**: Check `docker --version` and ensure Docker is running: `sudo systemctl status docker`
|
||||||
120
README.md
120
README.md
|
|
@ -1,65 +1,153 @@
|
||||||
# UniFi Access Airbnb Integration
|
|
||||||
|
|
||||||
This project integrates UniFi Access with Airbnb reservations, automating the process of creating and managing visitor access for your Airbnb guests.
|
This project integrates UniFi Access with Airbnb reservations, automating the process of creating and managing visitor access for your Airbnb guests.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Fetch reservations from Hostex API or Airbnb ICS feed
|
- Fetch reservations from Hostex API or Airbnb ICS feed
|
||||||
- Create UniFi Access visitor accounts for upcoming guests
|
- Create UniFi Access visitor accounts for upcoming guests
|
||||||
- Assign PIN codes to visitors based on their phone number
|
- Assign PIN codes to visitors based on their phone number
|
||||||
- Automatically delete past or completed visitor accounts
|
- Automatically delete past or completed visitor accounts
|
||||||
- Send notifications via Simplepush for updates and failures
|
- Send notifications via Simplepush for updates and failures
|
||||||
|
- Cross-verify reservations between Hostex and ICS calendar
|
||||||
|
- Monitor and report discrepancies in booking data
|
||||||
|
- Detailed logging of all visitor management operations
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Python 3.7+
|
- Python 3.7+
|
||||||
- UniFi Access system
|
- UniFi Access system
|
||||||
- Airbnb account with ICS feed URL or Hostex API access
|
- Airbnb account with ICS feed URL or Hostex API access
|
||||||
|
- [Optional] Simplepush account for notifications
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Clone the repository:
|
1. Clone the repository:
|
||||||
|
|
||||||
git clone https://github.com/yourusername/unifi-access-airbnb.git
|
git clone https://github.com/keithah/unifi-access-airbnb.git
|
||||||
|
|
||||||
cd unifi-access-airbnb
|
cd unifi-access-airbnb
|
||||||
|
|
||||||
3. Install the required packages:
|
2. Install the required packages:
|
||||||
|
|
||||||
|
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
||||||
4. Copy the example configuration file and edit it with your settings:
|
3. Copy the example configuration file and edit it with your settings:
|
||||||
|
|
||||||
cp unifi.conf.example unifi.conf
|
cp unifi.conf.example unifi.conf
|
||||||
|
|
||||||
nano unifi.conf
|
nano unifi.conf
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Run the script using:
|
Run the script using:
|
||||||
|
|
||||||
python3 main.py
|
python3 main.py
|
||||||
|
|
||||||
Optional arguments:
|
Optional arguments:
|
||||||
- `-v` or `--verbose`: Increase output verbosity
|
- `-v` or `--verbose`: Increase output verbosity
|
||||||
- `-l [LOG_FILE]` or `--log [LOG_FILE]`: Specify a log file
|
- `-l [LOG_FILE]` or `--log [LOG_FILE]`: Specify a log file (default: unifi_access.log)
|
||||||
- `--list-door-groups`: List available door groups
|
- `--list-door-groups`: List available door groups
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Edit the `unifi.conf` file with your specific settings. Key sections include:
|
Edit the `unifi.conf` file with your specific settings. Key sections include:
|
||||||
- `[UniFi]`: UniFi Access API settings
|
- `[UniFi]`: UniFi Access API settings
|
||||||
|
- api_host: UniFi Access controller URL
|
||||||
|
- api_token: Authentication token
|
||||||
- `[Hostex]`: Hostex API settings (if used)
|
- `[Hostex]`: Hostex API settings (if used)
|
||||||
|
- api_url: Hostex API endpoint
|
||||||
|
- api_key: Authentication key
|
||||||
- `[Airbnb]`: Airbnb ICS feed URL (if used)
|
- `[Airbnb]`: Airbnb ICS feed URL (if used)
|
||||||
- `[Door]`: Default door group ID for visitor access
|
- ics_url: Calendar feed URL
|
||||||
- `[Visitor]`: Check-in and check-out times
|
- `[Door]`: Door access settings
|
||||||
|
- default_group_id: Default door group ID for visitor access
|
||||||
|
- `[Visitor]`: Visit timing settings
|
||||||
|
- check_in_time: Default check-in time (e.g., "14:30")
|
||||||
|
- check_out_time: Default check-out time (e.g., "11:30")
|
||||||
|
- `[General]`: General settings
|
||||||
|
- log_file: Path to log file
|
||||||
|
- pin_code_digits: Number of digits for PIN codes
|
||||||
|
- `[Simplepush]`: Notification settings
|
||||||
|
- enabled: Enable/disable notifications
|
||||||
|
- key: Simplepush key
|
||||||
|
- url: Simplepush API URL
|
||||||
|
|
||||||
|
## Logging and Monitoring
|
||||||
|
The script provides detailed logging of all operations:
|
||||||
|
- Reservation processing from Hostex and ICS
|
||||||
|
- Visitor creation and deletion in UniFi Access
|
||||||
|
- PIN code assignments and updates
|
||||||
|
- Cross-system verification results
|
||||||
|
- Errors and warnings
|
||||||
|
|
||||||
|
Logs can be viewed in real-time using the `-v` flag or reviewed in the log file.
|
||||||
|
|
||||||
|
## System Verification
|
||||||
|
The script performs several verification checks:
|
||||||
|
- Matches Hostex reservations with ICS calendar entries
|
||||||
|
- Verifies phone numbers and PIN codes across systems
|
||||||
|
- Reports discrepancies in dates or guest information
|
||||||
|
- Monitors UniFi Access visitor status
|
||||||
|
|
||||||
|
## Docker Deployment
|
||||||
|
|
||||||
|
For easy deployment and automation, this project includes Docker support:
|
||||||
|
|
||||||
|
### Quick Start with Docker
|
||||||
|
```bash
|
||||||
|
# Build and run continuously (checks every 6 hours)
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Run once manually
|
||||||
|
docker-compose --profile manual up unifi-access-airbnb-once
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
See [README-DOCKER.md](README-DOCKER.md) for complete Docker setup instructions.
|
||||||
|
|
||||||
|
### Raspberry Pi Support
|
||||||
|
The Docker container runs perfectly on Raspberry Pi (ARM64/ARM32). Simply:
|
||||||
|
```bash
|
||||||
|
# On Raspberry Pi
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
Docker automatically pulls the correct architecture. See [README-DOCKER.md](README-DOCKER.md) for Pi-specific notes.
|
||||||
|
|
||||||
|
## Future Goals
|
||||||
|
|
||||||
|
### UniFi Protect Integration
|
||||||
|
Plans to extend this project with UniFi Protect video recording capabilities:
|
||||||
|
|
||||||
|
**Required Hardware:**
|
||||||
|
- UniFi Gateway Max (UDM-Pro Max) with built-in NVR
|
||||||
|
- OR UniFi Cloud Key Gen2+ with Protect
|
||||||
|
|
||||||
|
**Planned Features:**
|
||||||
|
- **Guest Check-in Recording**: Automatically start recording when guests arrive
|
||||||
|
- **Stay Duration Recording**: Record during entire guest stay period
|
||||||
|
- **Checkout Verification**: Capture departure recordings
|
||||||
|
- **Incident Documentation**: Archive recordings for property protection
|
||||||
|
- **Guest Privacy**: Automatic recording pause/resume based on occupancy
|
||||||
|
|
||||||
|
**Implementation Ideas:**
|
||||||
|
- Integrate with UniFi Protect API for camera control
|
||||||
|
- Coordinate recording schedules with visitor access periods
|
||||||
|
- Provide guest notification of recording periods (legal compliance)
|
||||||
|
- Archive system for easy retrieval of guest-specific recordings
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- Property protection during guest stays
|
||||||
|
- Incident documentation and liability protection
|
||||||
|
- Automated recording management tied to booking system
|
||||||
|
- Enhanced security without manual camera management
|
||||||
|
|
||||||
|
This would create a complete property automation ecosystem: **Access Control + Video Security** synchronized with your Airbnb bookings.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
If you encounter any issues or have questions:
|
||||||
|
1. Check the logs using verbose mode
|
||||||
|
2. Review your configuration file
|
||||||
|
3. Open an issue on GitHub with relevant logs and details
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
unifi-access-airbnb:
|
||||||
|
build: .
|
||||||
|
container_name: unifi-access-airbnb
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
# Mount config file (edit this on host)
|
||||||
|
- ./unifi.conf:/app/unifi.conf:ro
|
||||||
|
# Mount logs directory for persistence
|
||||||
|
- ./logs:/app/logs
|
||||||
|
environment:
|
||||||
|
- TZ=America/New_York # Set your timezone
|
||||||
|
# Resource limits for Raspberry Pi compatibility
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 128M
|
||||||
|
reservations:
|
||||||
|
memory: 64M
|
||||||
|
# Run every 6 hours by default
|
||||||
|
command: sh -c "while true; do python main.py; sleep 21600; done"
|
||||||
|
|
||||||
|
# Alternative: Run once and exit (for cron-like usage)
|
||||||
|
unifi-access-airbnb-once:
|
||||||
|
build: .
|
||||||
|
container_name: unifi-access-airbnb-once
|
||||||
|
profiles:
|
||||||
|
- manual # Only start with: docker-compose --profile manual up
|
||||||
|
volumes:
|
||||||
|
- ./unifi.conf:/app/unifi.conf:ro
|
||||||
|
- ./logs:/app/logs
|
||||||
|
environment:
|
||||||
|
- TZ=America/New_York
|
||||||
|
command: python main.py -v
|
||||||
|
|
@ -8,7 +8,7 @@ class HostexManager:
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def fetch_reservations(self):
|
def fetch_reservations(self):
|
||||||
url = f"{self.api_url}/reservations"
|
url = f"{self.api_url}/reservations?limit=100"
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {self.api_key}",
|
"Authorization": f"Bearer {self.api_key}",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@ class ICSParser:
|
||||||
end = event.get("DTEND").dt
|
end = event.get("DTEND").dt
|
||||||
description = event.get("DESCRIPTION", "")
|
description = event.get("DESCRIPTION", "")
|
||||||
if not description:
|
if not description:
|
||||||
self.logger.debug(f"Skipping event with start date {start.date()} due to missing description")
|
start_date = start if isinstance(start, datetime.date) else start.date()
|
||||||
|
self.logger.debug(f"Skipping event with start date {start_date} due to missing description")
|
||||||
continue
|
continue
|
||||||
pin_code = ""
|
pin_code = ""
|
||||||
for line in description.split("\n"):
|
for line in description.split("\n"):
|
||||||
|
|
@ -25,8 +26,8 @@ class ICSParser:
|
||||||
pin_code = line.split(": ")[1].strip()
|
pin_code = line.split(": ")[1].strip()
|
||||||
break
|
break
|
||||||
reservations.append({
|
reservations.append({
|
||||||
"check_in_date": start.date() if isinstance(start, datetime.datetime) else start,
|
"check_in_date": start if isinstance(start, datetime.date) else start.date(),
|
||||||
"check_out_date": end.date() if isinstance(end, datetime.datetime) else end,
|
"check_out_date": end if isinstance(end, datetime.date) else end.date(),
|
||||||
"guests": [{"name": "Airbnb Guest", "phone": pin_code}],
|
"guests": [{"name": "Airbnb Guest", "phone": pin_code}],
|
||||||
"status": "accepted"
|
"status": "accepted"
|
||||||
})
|
})
|
||||||
|
|
|
||||||
98
main.py
98
main.py
|
|
@ -1,6 +1,7 @@
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import urllib3
|
import urllib3
|
||||||
|
import datetime
|
||||||
from config import load_config
|
from config import load_config
|
||||||
from unifi_access import UnifiAccessManager
|
from unifi_access import UnifiAccessManager
|
||||||
from hostex_api import HostexManager
|
from hostex_api import HostexManager
|
||||||
|
|
@ -11,6 +12,48 @@ from utils import setup_logging
|
||||||
# Suppress InsecureRequestWarning
|
# Suppress InsecureRequestWarning
|
||||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
|
def verify_across_systems(hostex_reservations, ics_reservations, unifi_visitors):
|
||||||
|
discrepancies = []
|
||||||
|
today = datetime.date.today()
|
||||||
|
next_month = today + datetime.timedelta(days=30)
|
||||||
|
|
||||||
|
# Filter relevant Hostex reservations
|
||||||
|
relevant_hostex = [
|
||||||
|
r for r in hostex_reservations
|
||||||
|
if today <= datetime.datetime.strptime(r["check_in_date"], "%Y-%m-%d").date() <= next_month
|
||||||
|
and r["status"] == "accepted"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Create lookup dictionaries by date range
|
||||||
|
hostex_lookup = {
|
||||||
|
(r["check_in_date"], r["check_out_date"]): r
|
||||||
|
for r in relevant_hostex
|
||||||
|
}
|
||||||
|
|
||||||
|
ics_lookup = {
|
||||||
|
(r["check_in_date"].strftime("%Y-%m-%d"), r["check_out_date"].strftime("%Y-%m-%d")): r
|
||||||
|
for r in ics_reservations
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check Hostex entries against ICS
|
||||||
|
for dates, hostex_res in hostex_lookup.items():
|
||||||
|
if dates not in ics_lookup:
|
||||||
|
discrepancies.append(
|
||||||
|
f"Hostex reservation for {hostex_res['guests'][0]['name']} "
|
||||||
|
f"({dates[0]} to {dates[1]}) not found in ICS calendar"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Verify phone number last 4 digits match
|
||||||
|
hostex_phone = hostex_res['guests'][0].get('phone', '')[-4:]
|
||||||
|
ics_phone = ics_lookup[dates]['guests'][0].get('phone', '')[-4:]
|
||||||
|
if hostex_phone and ics_phone and hostex_phone != ics_phone:
|
||||||
|
discrepancies.append(
|
||||||
|
f"Phone number mismatch for {hostex_res['guests'][0]['name']}: "
|
||||||
|
f"Hostex: {hostex_phone}, ICS: {ics_phone}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return discrepancies
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="UniFi Access Visitor Management")
|
parser = argparse.ArgumentParser(description="UniFi Access Visitor Management")
|
||||||
parser.add_argument('-v', '--verbose', action='store_true', help="Increase output verbosity")
|
parser.add_argument('-v', '--verbose', action='store_true', help="Increase output verbosity")
|
||||||
|
|
@ -49,27 +92,60 @@ def main():
|
||||||
|
|
||||||
if config['use_hostex']:
|
if config['use_hostex']:
|
||||||
logger.info("Fetching reservations from Hostex")
|
logger.info("Fetching reservations from Hostex")
|
||||||
reservations = hostex_manager.fetch_reservations()
|
hostex_reservations = hostex_manager.fetch_reservations()
|
||||||
elif config['use_ics']:
|
|
||||||
logger.info("Parsing ICS file")
|
|
||||||
reservations = ics_parser.parse_ics()
|
|
||||||
else:
|
else:
|
||||||
logger.error("No valid reservation source configured")
|
hostex_reservations = []
|
||||||
return
|
|
||||||
|
if config['use_ics']:
|
||||||
logger.info(f"Processing {len(reservations)} reservations")
|
logger.info("Parsing ICS file")
|
||||||
unifi_manager.process_reservations(reservations)
|
ics_reservations = ics_parser.parse_ics()
|
||||||
|
else:
|
||||||
|
ics_reservations = []
|
||||||
|
|
||||||
|
# Filter and log relevant reservations
|
||||||
|
today = datetime.date.today()
|
||||||
|
next_month = today + datetime.timedelta(days=30)
|
||||||
|
relevant_reservations = [
|
||||||
|
r for r in hostex_reservations
|
||||||
|
if today <= datetime.datetime.strptime(r["check_in_date"], "%Y-%m-%d").date() <= next_month
|
||||||
|
and r["status"] == "accepted"
|
||||||
|
]
|
||||||
|
|
||||||
|
logger.info(f"Found {len(relevant_reservations)} entries in Hostex API within the next 30 days")
|
||||||
|
|
||||||
|
for res in relevant_reservations:
|
||||||
|
guest_name = res["guests"][0]["name"] if res["guests"] else "Guest"
|
||||||
|
phone_number = res["guests"][0].get("phone", "") if res["guests"] else ""
|
||||||
|
logger.debug(
|
||||||
|
f"Hostex Guest: {guest_name}, "
|
||||||
|
f"Stay: {res['check_in_date']} to {res['check_out_date']}, "
|
||||||
|
f"Phone: {phone_number}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify consistency across systems
|
||||||
|
discrepancies = verify_across_systems(
|
||||||
|
hostex_reservations,
|
||||||
|
ics_reservations,
|
||||||
|
unifi_manager.fetch_visitors()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Process reservations
|
||||||
|
logger.info(f"Processing {len(relevant_reservations)} reservations")
|
||||||
|
unifi_manager.process_reservations(relevant_reservations)
|
||||||
|
|
||||||
logger.info("Checking and updating PINs for existing visitors")
|
logger.info("Checking and updating PINs for existing visitors")
|
||||||
unifi_manager.check_and_update_pins()
|
unifi_manager.check_and_update_pins()
|
||||||
|
|
||||||
summary = unifi_manager.generate_summary()
|
summary = unifi_manager.generate_summary()
|
||||||
|
if discrepancies:
|
||||||
|
summary += "\n\nDiscrepancies Found:\n" + "\n".join(discrepancies)
|
||||||
|
|
||||||
logger.info(summary)
|
logger.info(summary)
|
||||||
|
|
||||||
total_visitors = len(unifi_manager.fetch_visitors())
|
total_visitors = len(unifi_manager.fetch_visitors())
|
||||||
logger.info(f"Total visitors remaining after cleanup: {total_visitors}")
|
logger.info(f"Total UniFi Access visitors remaining after cleanup: {total_visitors}")
|
||||||
|
|
||||||
if config['simplepush_enabled'] and unifi_manager.has_changes():
|
if config['simplepush_enabled'] and (unifi_manager.has_changes() or discrepancies):
|
||||||
notification_manager.send_notification("UniFi Access Update", summary)
|
notification_manager.send_notification("UniFi Access Update", summary)
|
||||||
logger.info("Simplepush notification sent")
|
logger.info("Simplepush notification sent")
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,10 @@ class UnifiAccessManager:
|
||||||
|
|
||||||
self.logger.debug(f"Loaded default_door_group_id from config: {self.default_door_group_id}")
|
self.logger.debug(f"Loaded default_door_group_id from config: {self.default_door_group_id}")
|
||||||
if not self.default_door_group_id:
|
if not self.default_door_group_id:
|
||||||
self.logger.warning("default_door_group_id is not set in config. Attempting to fetch available door groups.")
|
self.logger.error("default_group_id is not set in config. Please set it to your door device ID.")
|
||||||
self.set_default_door_group()
|
raise ValueError("default_group_id is required in config")
|
||||||
else:
|
else:
|
||||||
self.logger.debug(f"Initialized with default_door_group_id: {self.default_door_group_id}")
|
self.logger.debug(f"Initialized with door device ID: {self.default_door_group_id}")
|
||||||
|
|
||||||
def set_default_door_group(self):
|
def set_default_door_group(self):
|
||||||
door_groups = self.fetch_door_groups()
|
door_groups = self.fetch_door_groups()
|
||||||
|
|
@ -50,7 +50,7 @@ class UnifiAccessManager:
|
||||||
"resources": [
|
"resources": [
|
||||||
{
|
{
|
||||||
"id": self.default_door_group_id,
|
"id": self.default_door_group_id,
|
||||||
"type": "door_group"
|
"type": "door"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -220,19 +220,20 @@ class UnifiAccessManager:
|
||||||
def generate_summary(self):
|
def generate_summary(self):
|
||||||
summary = "Hostex-UniFi Access Summary:\n"
|
summary = "Hostex-UniFi Access Summary:\n"
|
||||||
unchanged_names = ", ".join(self.changes['unchanged'])
|
unchanged_names = ", ".join(self.changes['unchanged'])
|
||||||
summary += f"{len(self.changes['unchanged'])} existing visitors unchanged ({unchanged_names})\n"
|
summary += f"{len(self.changes['unchanged'])} existing UniFi Access visitors unchanged ({unchanged_names})\n"
|
||||||
if self.changes['deleted']:
|
if self.changes['deleted']:
|
||||||
deleted_names = ", ".join(self.changes['deleted'])
|
deleted_names = ", ".join(self.changes['deleted'])
|
||||||
summary += f"{len(self.changes['deleted'])} visitor(s) deleted ({deleted_names})\n"
|
summary += f"{len(self.changes['deleted'])} UniFi Access visitor(s) deleted ({deleted_names})\n"
|
||||||
if self.changes['added']:
|
if self.changes['added']:
|
||||||
added_names = ", ".join(self.changes['added'])
|
added_names = ", ".join(self.changes['added'])
|
||||||
summary += f"{len(self.changes['added'])} visitor(s) added ({added_names})\n"
|
summary += f"{len(self.changes['added'])} UniFi Access visitor(s) added ({added_names})\n"
|
||||||
return summary.strip()
|
return summary.strip()
|
||||||
|
|
||||||
def has_changes(self):
|
def has_changes(self):
|
||||||
return bool(self.changes['added'] or self.changes['deleted'])
|
return bool(self.changes['added'] or self.changes['deleted'])
|
||||||
|
|
||||||
def fetch_door_groups(self):
|
def fetch_door_groups(self):
|
||||||
|
# Try door_groups first, then user_groups if empty
|
||||||
url = f"{self.api_host}/api/v1/developer/door_groups"
|
url = f"{self.api_host}/api/v1/developer/door_groups"
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {self.api_token}",
|
"Authorization": f"Bearer {self.api_token}",
|
||||||
|
|
@ -240,6 +241,7 @@ class UnifiAccessManager:
|
||||||
}
|
}
|
||||||
response = requests.get(url, headers=headers, verify=False)
|
response = requests.get(url, headers=headers, verify=False)
|
||||||
self.logger.debug(f"Fetch door groups API response status code: {response.status_code}")
|
self.logger.debug(f"Fetch door groups API response status code: {response.status_code}")
|
||||||
|
self.logger.debug(f"Fetch door groups API response content: {response.text}")
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue