Agency-Phone Кастомные Приложения
Теперь с AgencyOS27: бесплатное обновление для всех, у кого уже есть телефон.

🧩 Своё приложение для Agency-Phone
Добавить приложение в телефон можно двумя способами. Выберите подходящий:
| Способ | Подходит для | Что вы меняете |
|---|---|---|
| A. SDK (внешний ресурс) | Разработчиков и скриптов, которые добавляют приложение со своей иконкой | Только свой ресурс. Никаких правок в agency-phone. |
| B. Drop-in (внутри agency-phone) | Владельцев серверов, которые добавляют нативный экран в интерфейс телефона | config.lua + apps/ (+ html/js для экрана) |
Если вы распространяете приложение на другие серверы, используйте способ A. Ему не нужен наш исходный код, и он продолжает работать после обновлений.
A SDK: регистрация приложения из своего ресурса
agency-phone предоставляет экспорты, с помощью которых любой ресурс во время работы добавляет приложение со своей иконкой на главный экран телефона. Интерфейс приложения живёт в вашем ресурсе; телефон показывает иконку и передаёт вам управление, когда игрок на неё нажимает. Без правок в agency-phone и без разблокированной сборки.
Минимальный пример
-- client side of YOUR resource
CreateThread(function()
Wait(1500) -- let agency-phone boot
exports['agency-phone']:RegisterApp({
id = 'myshop',
label = 'My Shop',
icon = 'fa-solid fa-cart-shopping', -- FontAwesome 6 class
-- image = 'https://cfx-nui-myresource/icon.png', -- or your own image instead
color = '#ff9500',
colorDark = '#e08600',
})
end)
-- fired when the player taps your icon on the phone
AddEventHandler('agency-phone:client:openApp', function(appId)
if appId ~= 'myshop' then return end
exports['agency-phone']:ClosePhone()
-- open your own NUI here
end)
Параметры RegisterApp
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
id | string | да | Уникальный id, строчными буквами, без пробелов. |
label | string | да | Название под иконкой. |
icon | string | нет | Класс FontAwesome 6. Игнорируется, если задан image. |
image | string | нет | Своя картинка иконки, например https://cfx-nui-<resource>/icon.png. |
color / colorDark | string | нет | Градиент за иконкой (hex). |
slot | number | нет | Позиция на главном экране. Если не указана, выбирается автоматически. |
storeDesc | string | нет | Краткое описание на странице приложения в Store. Если не указано, используется label. |
Экспорты SDK (клиент)
| Экспорт | Описание |
|---|---|
RegisterApp(config) | Добавить или обновить приложение. Возвращает true или false, err. |
UnregisterApp(id) | Удалить приложение. Происходит и автоматически, когда ваш ресурс останавливается. |
GetRegisteredApps() | Таблица всех зарегистрированных внешних приложений. |
ClosePhone() | Закрыть телефон, например перед открытием своего NUI. |
Событие нажатия
Когда игрок нажимает на вашу иконку, agency-phone вызывает на клиенте оба события. Используйте любое:
| Событие | Аргументы |
|---|---|
agency-phone:client:openApp | appId (фильтруйте по своему id) |
agency-phone:client:openApp:<yourId> | нет (уже относится только к вашему приложению) |
Добавьте dependency 'agency-phone' в свой fxmanifest.lua, чтобы порядок запуска был правильным.
B Drop-in, нативный экран внутри телефона
Для экрана, который рисует сам телефон (а не ваш ресурс). При этом меняются файлы agency-phone.
| Шаг | Файл | Можно менять в escrow-сборке? |
|---|---|---|
1. Запись приложения в AgencyConfig.Apps | config.lua | ✓ да |
2. Логика RegisterNUICallback | apps/<id>_client.lua (+ _server.lua) | ✓ да |
3. Экран <div id="app-<id>"> | html/index.html | ✗ зашифрован |
4. case '<id>': в openApp() + загрузчик | html/js/phone.js | ✗ зашифрован |
Шаги 3 и 4 находятся в файлах, зашифрованных escrow, поэтому нативному экрану нужна сборка с исходниками или разблокированная сборка. Если у вас только escrow-версия, используйте способ A (экран живёт в вашем ресурсе). Примеры внутри ресурса: apps/testapp_client.lua и apps/dispatch_client.lua.
📋 Другие экспорты телефона
Клиент
| Экспорт | Возвращает | Описание |
|---|---|---|
IsPhoneOpen() | boolean | Телефон сейчас открыт. |
IsPhonePoweredOn() | boolean | Устройство включено. |
HasPhone() | boolean | У игрока есть предмет телефона. |
HasCarrierContract() | boolean | Активная eSIM или контракт. |
GetPhoneData() | table | Снимок состояния телефона. |
PhoneNotification(title, text, icon, color, timeout, acceptIcon, denyIcon) | void | Уведомление на телефоне. |
StartCall(contact) | void | Звонок на { name, number }. |
StartAgencyPayCheckout(opts) | void | Оплата AgencyPay (см. ниже). |
Сервер
| Экспорт | Описание |
|---|---|
SendMail(identifier, mailData) | Доставить письмо игроку (онлайн или офлайн). |
AddChirp(data) | Опубликовать сообщение в соцленте Pulse. |
VerifyAgencyPayCharge(source, requestId, minAmount) | Подтвердить на сервере оплаченный счёт AgencyPay перед выдачей товара (см. ниже). |
⇄ Мост NUI ↔ Lua (способ B)
| Направление | Сторона JS | Сторона Lua |
|---|---|---|
| JS → Lua | fetchNUI('callbackName', data) | RegisterNUICallback('callbackName', fn) |
| Lua → JS | слушайте action в window.onmessage | SendNUIMessage({ action = '...' }) |
💳 Оплата AgencyPay
Списывайте деньги с кошелька игрока из любого ресурса.
exports['agency-phone']:StartAgencyPayCheckout({
merchant = 'My Shop', title = 'Agency Pay', subtitle = 'Checkout',
label = 'Premium Coffee', amount = 24.50,
icon = 'fa-solid fa-mug-hot', accent = '#ff9f0a',
clientEvent = 'myshop:client:agencyPayResult',
serverEvent = 'myshop:server:agencyPayResult',
metadata = { orderId = 'order_2048' },
})
RegisterNetEvent('myshop:client:agencyPayResult', function(result)
if result.status == 'paid' then
-- deliver the goods
end
end)
Результат содержит status (paid / cancelled / failed), amount, cardId, balance и ваши metadata.
Результат paid, пришедший от клиента в ваш ресурс, сам по себе ничего не доказывает. Передайте свой id в StartAgencyPayCheckout и перед выдачей чего-либо вызовите на сервере exports['agency-phone']:VerifyAgencyPayCharge(source, id, amount). Он возвращает true и сумму, только если этот игрок оплатил этот счёт за последние 60 секунд, и списывает платёж, чтобы один и тот же платёж нельзя было засчитать дважды.
🐛 Решение проблем
| Симптом | Решение |
|---|---|
| Иконка не появляется (SDK) | Обновите agency-phone до последней сборки Keymaster, добавьте Wait(1500) перед RegisterApp и dependency 'agency-phone' в манифест. |
| Нажатие ничего не делает (SDK) | Ваш обработчик agency-phone:client:openApp должен точно совпадать с вашим id (с учётом регистра). |
| Своя картинка не отображается | Используйте полный NUI-адрес https://cfx-nui-<your-resource>/path/icon.png и перечислите файл в files { } манифеста. |
| Иконка не появляется (drop-in) | enabled = true и installed = true в записи конфига. |
Есть вопросы или нужна помощь? Заходите в Discord Agency Scripts.