QB / ESX
Agency-LifeinvaderV3
Modern advertisement system for FiveM with custom Liquid Glass UI, tiered pricing based on character count, and ad queue management. Supports ad categories with filtering, priority/boost ads, anonymous posting, and database persistence via oxmysql. Includes admin panel for approving/deleting ads and works with ESX, QBCore, and Standalone.

01 Installation
Extract to resources directory
Import agency_lifeinvader.sql (optional, has auto-upgrader)
Add ensure Agency-LifeinvaderV3 to server.cfg
Configure config.lua (framework, pricing, notifications)
Dependencies
oxmysql — required if using databaseESX or QBCore — optional, supports standalone
SQL File
Import this SQL file into your database before starting the resource:
-- ═══════════════════════════════════════════
-- AGENCY LIFEINVADER V3 - DATABASE SETUP
-- by Agency Scripts
-- ═══════════════════════════════════════════
CREATE TABLE IF NOT EXISTS `lifeinvader_ads` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`identifier` VARCHAR(100) NOT NULL,
`author_name` VARCHAR(100) DEFAULT 'Anonymous',
`message` TEXT NOT NULL,
`category` VARCHAR(50) DEFAULT 'other',
`anonymous` TINYINT DEFAULT 0,
`boosted` TINYINT DEFAULT 0,
`repetitions_total` INT DEFAULT 1,
`repetitions_left` INT DEFAULT 1,
`interval_seconds` INT DEFAULT 60,
`display_duration` INT DEFAULT 8,
`status` ENUM('pending','active','completed','rejected') DEFAULT 'active',
`approved` TINYINT DEFAULT 1,
`approved_by` VARCHAR(100) DEFAULT NULL,
`rejected_reason` TEXT DEFAULT NULL,
`next_send_time` DATETIME DEFAULT NULL,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX `idx_identifier` (`identifier`),
INDEX `idx_status` (`status`),
INDEX `idx_approved` (`approved`),
INDEX `idx_category` (`category`),
INDEX `idx_boosted` (`boosted`),
INDEX `idx_next_send` (`next_send_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `lifeinvader_player_settings` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`identifier` VARCHAR(100) NOT NULL UNIQUE,
`notification_position` VARCHAR(20) DEFAULT '0,50',
`notification_enabled` TINYINT DEFAULT 1,
`notification_scale` FLOAT DEFAULT 1.0,
`ui_transparency` FLOAT DEFAULT 0.55,
`accent_color` VARCHAR(20) DEFAULT '#3b82f6',
`sound_enabled` TINYINT DEFAULT 1,
`admin_panel_scale` FLOAT DEFAULT 1.0,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX `idx_identifier` (`identifier`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `lifeinvader_ad_drafts` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`identifier` VARCHAR(100) NOT NULL,
`title` VARCHAR(120) DEFAULT NULL,
`message` TEXT NOT NULL,
`category` VARCHAR(50) DEFAULT 'other',
`anonymous` TINYINT DEFAULT 0,
`boosted` TINYINT DEFAULT 0,
`repetitions_total` INT DEFAULT 1,
`interval_seconds` INT DEFAULT 60,
`display_duration` INT DEFAULT 8,
`delay` INT DEFAULT 0,
`scheduled_for` DATETIME DEFAULT NULL,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX `idx_identifier` (`identifier`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `lifeinvader_admin_logs` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`admin_identifier` VARCHAR(100) DEFAULT NULL,
`admin_name` VARCHAR(100) DEFAULT NULL,
`action` VARCHAR(64) NOT NULL,
`ad_id` INT DEFAULT NULL,
`metadata` LONGTEXT DEFAULT NULL,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX `idx_action` (`action`),
INDEX `idx_ad_id` (`ad_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ═══ AUTO UPGRADE SYSTEM ═══
-- Safely adds missing columns without throwing errors on existing databases
DROP PROCEDURE IF EXISTS `UpgradeLifeinvaderColumns`;
DELIMITER //
CREATE PROCEDURE `UpgradeLifeinvaderColumns`()
BEGIN
DECLARE _dbName VARCHAR(255) DEFAULT DATABASE();
DECLARE cnt INT;
-- Add category column
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'category';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `category` VARCHAR(50) DEFAULT 'other' AFTER `message`;
END IF;
-- Add anonymous column
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'anonymous';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `anonymous` TINYINT DEFAULT 0 AFTER `category`;
END IF;
-- Add boosted column
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'boosted';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `boosted` TINYINT DEFAULT 0 AFTER `anonymous`;
END IF;
-- Add ui_transparency column to player settings
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_player_settings' AND COLUMN_NAME = 'ui_transparency';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_player_settings` ADD COLUMN `ui_transparency` FLOAT DEFAULT 0.55 AFTER `notification_scale`;
END IF;
-- Add accent_color column to player settings
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_player_settings' AND COLUMN_NAME = 'accent_color';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_player_settings` ADD COLUMN `accent_color` VARCHAR(20) DEFAULT '#3b82f6' AFTER `ui_transparency`;
END IF;
-- Add admin_panel_scale column to player settings
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_player_settings' AND COLUMN_NAME = 'admin_panel_scale';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_player_settings` ADD COLUMN `admin_panel_scale` FLOAT DEFAULT 1.0 AFTER `sound_enabled`;
END IF;
-- Add display_duration column to drafts
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ad_drafts' AND COLUMN_NAME = 'display_duration';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ad_drafts` ADD COLUMN `display_duration` INT DEFAULT 8 AFTER `interval_seconds`;
END IF;
-- Add delay column to drafts
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ad_drafts' AND COLUMN_NAME = 'delay';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ad_drafts` ADD COLUMN `delay` INT DEFAULT 0 AFTER `display_duration`;
END IF;
-- Add scheduling / review / AI fields
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'scheduled_for';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `scheduled_for` DATETIME DEFAULT NULL AFTER `next_send_time`;
END IF;
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'review_requested_at';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `review_requested_at` DATETIME DEFAULT NULL AFTER `approved`;
END IF;
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'reviewed_at';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `reviewed_at` DATETIME DEFAULT NULL AFTER `review_requested_at`;
END IF;
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'review_decision';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `review_decision` VARCHAR(32) DEFAULT NULL AFTER `reviewed_at`;
END IF;
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'queue_priority';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `queue_priority` INT DEFAULT 0 AFTER `review_decision`;
END IF;
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'ai_score';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `ai_score` INT DEFAULT 0 AFTER `queue_priority`;
END IF;
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'ai_flags';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `ai_flags` LONGTEXT DEFAULT NULL AFTER `ai_score`;
END IF;
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'admin_notes';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `admin_notes` LONGTEXT DEFAULT NULL AFTER `ai_flags`;
END IF;
-- Add display_duration column
SELECT COUNT(*) INTO cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = _dbName AND TABLE_NAME = 'lifeinvader_ads' AND COLUMN_NAME = 'display_duration';
IF cnt = 0 THEN
ALTER TABLE `lifeinvader_ads` ADD COLUMN `display_duration` INT DEFAULT 8 AFTER `interval_seconds`;
END IF;
-- Indexes cannot be safely checked inline as easily in older MySQL,
-- but usually they don't break queries if missing. We only add them on fresh install via CREATE TABLE above.
END //
DELIMITER ;
CALL UpgradeLifeinvaderColumns();
DROP PROCEDURE IF EXISTS `UpgradeLifeinvaderColumns`;