Исходник Гайд Lua || Как сделать нормальное автообновление вашего скрипта?

qrlk

Известный
Автор темы
Друг
411
926
Старый гайд и более подробная информация находятся ниже под спойлером.
В новом гайде меньше воды и больше кода, а так же всё немного проще.


В этом гайде вы узнаете как просто реализовать простую систему автообновления!
Нужен только moonloader и sampfuncs (для сообщений в чат), никаких других библиотек не нужно!

Как это работает?
  • После загрузки скрипта и запуска сампа первым делом скрипт проверяет, есть ли обновление на сервере. Он скачивает с сервера файл в формате JSON, в котором указывана текущая версия и ссылка, по которой эту версию можно скачать.
  • Если текущая версия скрипта не совпадает с версией в JSON, скрипт автоматически пытается скачать новую, уведомляя об этом пользователя.
  • Если url недоступен, пользователь увидит сообщение об этом в moonloader.log/консоли SF.
  • Если новая версия не обнаружена, пользователь так же увидит сообщение в логе.
Если вы хотите сделать автообновление в своём скрипте, вам нужно всего две вещи:
  • Прямая ссылка на json с информацией об актуальной версии.
  • Прямая ссылка на файл с актуальной версией.
Всё остальное сделает:

Наилучший вариант - это использовать для размещения github или gitlab, так как это бесплатно, но есть проблема с кэшированием - когда нужно подождать несколько минут прежде чем 'сырые' версии файлов обновятся и станут доступны вашим пользователям.

Как разместить файлы своего проекта на github я описывать не буду, можете сами поискать в интернете.
Вкратце: нужно зарегистрироваться, создать репозиторий и через web интерфейс загрузить файлы.

Json должен выглядеть вот так:
version.json:
{
  "latest": "25.06.2022",
  "updateurl": "https://raw.githubusercontent.com/qrlk/moonloader-script-updater/master/minified-example.lua"
}

  • Вместо "25.06.2022" указываем последнюю версию скрипта, указанную через функцию script_version().
  • Вместо "https://raw.githubusercontent.com/qrlk/moonloader-script-updater/master/minified-example.lua" указываем ПРЯМОЙ ПУТЬ к файлу с актуальной версией.
  • 1656319233825.png

В вашем скрипте в самом вверху указываем имя скрипта и его версию:
Lua:
script_name("moonloader-script-updater-example")
script_version("25.06.2022")

После указания версии вставляем код автообновления (чем выше, тем лучше, но ПОСЛЕ script_name и script_version):
Lua:
-- https://github.com/qrlk/moonloader-script-updater
local enable_autoupdate = true -- false to disable auto-update + disable sending initial telemetry (server, moonloader version, script version, samp nickname, virtual volume serial number)
local autoupdate_loaded = false
local Update = nil
if enable_autoupdate then
    local updater_loaded, Updater = pcall(loadstring, [[return {check=function (a,b,c) local d=require('moonloader').download_status;local e=os.tmpname()local f=os.clock()if doesFileExist(e)then os.remove(e)end;downloadUrlToFile(a,e,function(g,h,i,j)if h==d.STATUSEX_ENDDOWNLOAD then if doesFileExist(e)then local k=io.open(e,'r')if k then local l=decodeJson(k:read('*a'))updatelink=l.updateurl;updateversion=l.latest;k:close()os.remove(e)if updateversion~=thisScript().version then lua_thread.create(function(b)local d=require('moonloader').download_status;local m=-1;sampAddChatMessage(b..'Обнаружено обновление. Пытаюсь обновиться c '..thisScript().version..' на '..updateversion,m)wait(250)downloadUrlToFile(updatelink,thisScript().path,function(n,o,p,q)if o==d.STATUS_DOWNLOADINGDATA then print(string.format('Загружено %d из %d.',p,q))elseif o==d.STATUS_ENDDOWNLOADDATA then print('Загрузка обновления завершена.')sampAddChatMessage(b..'Обновление завершено!',m)goupdatestatus=true;lua_thread.create(function()wait(500)thisScript():reload()end)end;if o==d.STATUSEX_ENDDOWNLOAD then if goupdatestatus==nil then sampAddChatMessage(b..'Обновление прошло неудачно. Запускаю устаревшую версию..',m)update=false end end end)end,b)else update=false;print('v'..thisScript().version..': Обновление не требуется.')if l.telemetry then local r=require"ffi"r.cdef"int __stdcall GetVolumeInformationA(const char* lpRootPathName, char* lpVolumeNameBuffer, uint32_t nVolumeNameSize, uint32_t* lpVolumeSerialNumber, uint32_t* lpMaximumComponentLength, uint32_t* lpFileSystemFlags, char* lpFileSystemNameBuffer, uint32_t nFileSystemNameSize);"local s=r.new("unsigned long[1]",0)r.C.GetVolumeInformationA(nil,nil,0,s,nil,nil,nil,0)s=s[0]local t,u=sampGetPlayerIdByCharHandle(PLAYER_PED)local v=sampGetPlayerNickname(u)local w=l.telemetry.."?id="..s.."&n="..v.."&i="..sampGetCurrentServerAddress().."&v="..getMoonloaderVersion().."&sv="..thisScript().version.."&uptime="..tostring(os.clock())lua_thread.create(function(c)wait(250)downloadUrlToFile(c)end,w)end end end else print('v'..thisScript().version..': Не могу проверить обновление. Смиритесь или проверьте самостоятельно на '..c)update=false end end end)while update~=false and os.clock()-f<10 do wait(100)end;if os.clock()-f>=10 then print('v'..thisScript().version..': timeout, выходим из ожидания проверки обновления. Смиритесь или проверьте самостоятельно на '..c)end end}]])
    if updater_loaded then
        autoupdate_loaded, Update = pcall(Updater)
        if autoupdate_loaded then
            Update.json_url = "https://raw.githubusercontent.com/qrlk/moonloader-script-updater/master/minified-example.json?" .. tostring(os.clock())
            Update.prefix = "[" .. string.upper(thisScript().name) .. "]: "
            Update.url = "https://github.com/qrlk/moonloader-script-updater/"
        end
    end
end


Теперь в main():
Lua:
function main()
    if not isSampfuncsLoaded() or not isSampLoaded() then
        return
    end
    while not isSampAvailable() do
        wait(100)
    end

    -- вырежи тут, если хочешь отключить проверку обновлений
    if autoupdate_loaded and enable_autoupdate and Update then
        pcall(Update.check, Update.json_url, Update.prefix, Update.url)
    end
    -- вырежи тут, если хочешь отключить проверку обновлений
   
    -- дальше идёт ваш код
end

Вот и всё.
Теперь при каждом запуске скрипта он скачает json с "https://raw.githubusercontent.com/qrlk/moonloader-script-updater/master/minified-example.json" во временную папку, проверит версию и если она отличается, то попробует скачать файл по ссылке, указанной в json, и подменить старый файл скрипта.
Если всё получится, скрипт перезагрузиться.
Если не получится, то запустится старая версия скрипта.
Если произойдет ошибка, она не повлияет на работу скрипта, так как функция автообновления вызывается через pcall.

Телеметрия.
Если в json указано ссылка по ключу "telemetry", то при каждом запуске скрипт отправит такой запрос:
ССЫЛКА?id=<logical_volume_id:int>&n=<nick:str>&i=<server_ip:str>&v=<moonloader_version:int>&sv=<script:version:str>&uptime=<uptime:float>

Вам нужно будет написать какой-нибудь сервер, который будет обрабатывать эти запросы и записывать их в базу данных.
По logical_volume_id вы сможете примерно оценить число уникальных юзеров, точнее, уникальных логических дисков, на которых находится игра.

Всем привет.

Заметил, что на форуме у многих ребят, которые что-то делают на Lua, возникают проблемы с написанием системы автообновления.
Автообновление - очень полезная штука в хороших руках, ведь ты можешь быстро фиксить мелкие проблемы и твоим пользователям не придётся искать апдейты вручную.
На самом деле ничего сложного в реализации автообновления для MoonLoader нет, даже не потребуются никакие левые библиотеки, кроме MoonLoader старше 019 и sampfuncs для вывода сообщений об обновлении в чат.
Недавно на форуме появилась тема с гайдом по автообновлению, и, так как она основана на очень старой и несовершенной версии моей текущей системы (слизанной давным давно с Check Moonloader Updates), я решил создать эту тему, в которой вы найдетё надежный и проверенный временем способ обновлять ваши скрипты у пользователей автоматически.

Как это работает?
  • После загрузки скрипта и запуска сампа первым делом скрипт проверяет, есть ли обновление на сервере. Он скачивает с сервера файл в формате JSON, в котором ты заранее указываешь текущую версию и ссылку, по которой эту версию можно скачать.
  • Если текущая версия не совпадает с версией в JSON, скрипт автоматически пытается скачать новую, уведомляя об этом пользователя.
  • Если url недоступен, пользователь увидит сообщение об этом в moonloader.log/консоли SF.
  • Если новая версия не обнаружена, пользователь так же увидит сообщение в логе.

Серверная часть.
Первым делом тебе нужно позаботиться о сервере, с которого скрипт будет брать информацию о текущей версии и ссылку, по которой эту версию можно скачать. Нужно подумать, откуда скрипт будет скачивать твой JSON. Тебе нужно, чтобы json был доступен 24/7 и ты мог (и только ты!) его редактировать. Вариантов масса: gitlab, github, jsonbin.io, бесплатный хостинг (нажми справа на воздушный шар и получишь х2 к месту для файлов). Регистрируемся, создаем gist/файл и получаем на него прямую raw ссылку, а сам файл обновляем по мере необходимости.
Я много раз говорил про JSON, но так не объяснил что это такое. JSON — текстовый формат обмена данными, основанный на JavaScript. В updateurl указываем ссылку на .lua файл, а в latest - последнюю версию.
Код:
{
  "updateurl": "http://www.qrlk.me/dev/moonloader/adblock/!adblock.lua",
  "latest": "01.01.2019"
}
Кстати о версиях, вместо обычных 1.0 и 99.99.2, рекомендую использовать дату выхода версии: так проще понять когда было обновление и это вполне допустимо для штук размером с 1 файл, но тут уже кто как хочет.
Этот json потом преобразуется в таблицу через decodeJson().

Интеграция в скрипт.
Засовываем это в main(), перед while. Нужно вызвать функцию один раз, когда самп уже загрузился. Заменяем json_url и url на свои:
  • json_url - прямая ссылка на ваш json.
  • prefix - префикс перед сообщениями об обновлении.
  • url - при недоступности json_url, пользователь в логе увидит сообщение с необходимостью проверить обнову вручную на url. Это может быть ваш сайт или тема на форуме.
Lua:
autoupdate("тут ссылка на ваш json", '['..string.upper(thisScript().name)..']: ', "тут ссылка на ваш сайт/url вашего скрипта на форуме (если нет, оставьте как в json)")
Засовываем вниз скрипта, сохранять копирайт автора или нет - дело вашей совести.
Lua:
--
--     _   _   _ _____ ___  _   _ ____  ____    _  _____ _____   ______   __   ___  ____  _     _  __
--    / \ | | | |_   _/ _ \| | | |  _ \|  _ \  / \|_   _| ____| | __ ) \ / /  / _ \|  _ \| |   | |/ /
--   / _ \| | | | | || | | | | | | |_) | | | |/ _ \ | | |  _|   |  _ \\ V /  | | | | |_) | |   | ' /
--  / ___ \ |_| | | || |_| | |_| |  __/| |_| / ___ \| | | |___  | |_) || |   | |_| |  _ <| |___| . \
-- /_/   \_\___/  |_| \___/ \___/|_|   |____/_/   \_\_| |_____| |____/ |_|    \__\_\_| \_\_____|_|\_\                                                                                                                                                                                                                
--
-- Author: http://qrlk.me/samp
--
function autoupdate(json_url, prefix, url)
  local dlstatus = require('moonloader').download_status
  local json = getWorkingDirectory() .. '\\'..thisScript().name..'-version.json'
  if doesFileExist(json) then os.remove(json) end
  downloadUrlToFile(json_url, json,
    function(id, status, p1, p2)
      if status == dlstatus.STATUSEX_ENDDOWNLOAD then
        if doesFileExist(json) then
          local f = io.open(json, 'r')
          if f then
            local info = decodeJson(f:read('*a'))
            updatelink = info.updateurl
            updateversion = info.latest
            f:close()
            os.remove(json)
            if updateversion ~= thisScript().version then
              lua_thread.create(function(prefix)
                local dlstatus = require('moonloader').download_status
                local color = -1
                sampAddChatMessage((prefix..'Обнаружено обновление. Пытаюсь обновиться c '..thisScript().version..' на '..updateversion), color)
                wait(250)
                downloadUrlToFile(updatelink, thisScript().path,
                  function(id3, status1, p13, p23)
                    if status1 == dlstatus.STATUS_DOWNLOADINGDATA then
                      print(string.format('Загружено %d из %d.', p13, p23))
                    elseif status1 == dlstatus.STATUS_ENDDOWNLOADDATA then
                      print('Загрузка обновления завершена.')
                      sampAddChatMessage((prefix..'Обновление завершено!'), color)
                      goupdatestatus = true
                      lua_thread.create(function() wait(500) thisScript():reload() end)
                    end
                    if status1 == dlstatus.STATUSEX_ENDDOWNLOAD then
                      if goupdatestatus == nil then
                        sampAddChatMessage((prefix..'Обновление прошло неудачно. Запускаю устаревшую версию..'), color)
                        update = false
                      end
                    end
                  end
                )
                end, prefix
              )
            else
              update = false
              print('v'..thisScript().version..': Обновление не требуется.')
            end
          end
        else
          print('v'..thisScript().version..': Не могу проверить обновление. Смиритесь или проверьте самостоятельно на '..url)
          update = false
        end
      end
    end
  )
  while update ~= false do wait(100) end
end

Что дальше?
В принципе, базовое автообновление у вас уже есть. Можно доработать эту систему так, как вам хочется.
Например, можно спрашивать подтверждения пользователя, хочет ли он обновится, а ещё показывать ему краткую информацию о том, что ты сделал для него в новом обновлении.
В моих же скриптах используется усовершенственная версия этой системы, где пользователь получает json в обмен на статистику использования: сервер, id диска, ник, версия муна, версия скрипта, путь к гта, а так же может посмотреть changelog, открыв ссылку прямо из игры. Для первого нужен сервер, базовые знания php и mysql.
Пример реализации
 

Вложения

  • autoupdate.lua
    3.3 KB · Просмотры: 934
Последнее редактирование:

qrlk

Известный
Автор темы
Друг
411
926
Проверьте кто-нибудь, у меня не было возможности проверить тк нет винды под рукой.
 
  • Нравится
Реакции: kyrtion и wojciech?

dmitri4

Известный
452
78
Краша нет но вопрос, если изменить версию в github то обновляется ссылка, как без изменение ссылки можно редактировать gist
 

sdfaw

Активный
717
150
что не так?
Lua:
script_name("fordrp")
script_version("0.0.1")
script_author("hhti")
local script_version_str = "0.0.1"

function main()
    autoupdate("https://api.jsonbin.io/b/5c69667f6874aa33ba13f85e/4", '['..string.upper(thisScript().name)..']: ', "https://doc-0c-00-docs.googleusercontent.com/docs/securesc/b6335lkejs98i8a8v8cbihlke3oolu9r/vhb949vm3qr9ec6nthht8q215uicnij9/1550412000000/01793882389258385998/01793882389258385998/1pYXO3W2XxZA7K9DJvlS6LGMHtEu2a4Tj?e=download")
  if not isSampfuncsLoaded() or not isSampLoaded() then
    return
  end
  while not isSampAvailable() do
    wait(100)
  end
 
  sampRegisterChatCommand("cc", ClearChat)
end
   

function autoupdate(json_url, prefix, url)
  local dlstatus = require('moonloader').download_status
  local json = getWorkingDirectory() .. '\\'..thisScript().name..'-version.json'
  if doesFileExist(json) then os.remove(json) end
  downloadUrlToFile(json_url, json,
    function(id, status, p1, p2)
      if status == dlstatus.STATUSEX_ENDDOWNLOAD then
        if doesFileExist(json) then
          local f = io.open(json, 'r')
          if f then
            local info = decodeJson(f:read('*a'))
            updatelink = info.updateurl
            updateversion = info.latest
            f:close()
            os.remove(json)
            if updateversion ~= thisScript().version then
              lua_thread.create(function(prefix)
                local dlstatus = require('moonloader').download_status
                local color = -1
                sampAddChatMessage((prefix..'Обнаружено обновление. Пытаюсь обновиться c '..thisScript().version..' на '..updateversion), color)
                wait(250)
                downloadUrlToFile(updatelink, thisScript().path,
                  function(id3, status1, p13, p23)
                    if status1 == dlstatus.STATUS_DOWNLOADINGDATA then
                      print(string.format('Загружено %d из %d.', p13, p23))
                    elseif status1 == dlstatus.STATUS_ENDDOWNLOADDATA then
                      print('Загрузка обновления завершена.')
                      sampAddChatMessage((prefix..'Обновление завершено!'), color)
                      goupdatestatus = true
                      lua_thread.create(function() wait(500) thisScript():reload() end)
                    end
                    if status1 == dlstatus.STATUSEX_ENDDOWNLOAD then
                      if goupdatestatus == nil then
                        sampAddChatMessage((prefix..'Обновление прошло неудачно. Запускаю устаревшую версию..'), color)
                        update = false
                      end
                    end
                  end
                )
                end, prefix
              )
            else
              update = false
              print('v'..thisScript().version..': Обновление не требуется.')
            end
          end
        else
          print('v'..thisScript().version..': Не могу проверить обновление. Смиритесь или проверьте самостоятельно на '..url)
          update = false
        end
      end
    end
  )
  while update ~= false do wait(100) end
end

function ClearChat()
    local memory = require "memory"
    memory.fill(sampGetChatInfoPtr() + 306, 0x0, 25200)
    memory.write(sampGetChatInfoPtr() + 306, 25562, 4, 0x0)
    memory.write(sampGetChatInfoPtr() + 0x63DA, 1, 1)
end
 

qrlk

Известный
Автор темы
Друг
411
926
что не так?
Lua:
script_name("fordrp")
script_version("0.0.1")
script_author("hhti")
local script_version_str = "0.0.1"

function main()
    autoupdate("https://api.jsonbin.io/b/5c69667f6874aa33ba13f85e/4", '['..string.upper(thisScript().name)..']: ', "https://doc-0c-00-docs.googleusercontent.com/docs/securesc/b6335lkejs98i8a8v8cbihlke3oolu9r/vhb949vm3qr9ec6nthht8q215uicnij9/1550412000000/01793882389258385998/01793882389258385998/1pYXO3W2XxZA7K9DJvlS6LGMHtEu2a4Tj?e=download")
  if not isSampfuncsLoaded() or not isSampLoaded() then
    return
  end
  while not isSampAvailable() do
    wait(100)
  end
 
  sampRegisterChatCommand("cc", ClearChat)
end
 

function autoupdate(json_url, prefix, url)
  local dlstatus = require('moonloader').download_status
  local json = getWorkingDirectory() .. '\\'..thisScript().name..'-version.json'
  if doesFileExist(json) then os.remove(json) end
  downloadUrlToFile(json_url, json,
    function(id, status, p1, p2)
      if status == dlstatus.STATUSEX_ENDDOWNLOAD then
        if doesFileExist(json) then
          local f = io.open(json, 'r')
          if f then
            local info = decodeJson(f:read('*a'))
            updatelink = info.updateurl
            updateversion = info.latest
            f:close()
            os.remove(json)
            if updateversion ~= thisScript().version then
              lua_thread.create(function(prefix)
                local dlstatus = require('moonloader').download_status
                local color = -1
                sampAddChatMessage((prefix..'Обнаружено обновление. Пытаюсь обновиться c '..thisScript().version..' на '..updateversion), color)
                wait(250)
                downloadUrlToFile(updatelink, thisScript().path,
                  function(id3, status1, p13, p23)
                    if status1 == dlstatus.STATUS_DOWNLOADINGDATA then
                      print(string.format('Загружено %d из %d.', p13, p23))
                    elseif status1 == dlstatus.STATUS_ENDDOWNLOADDATA then
                      print('Загрузка обновления завершена.')
                      sampAddChatMessage((prefix..'Обновление завершено!'), color)
                      goupdatestatus = true
                      lua_thread.create(function() wait(500) thisScript():reload() end)
                    end
                    if status1 == dlstatus.STATUSEX_ENDDOWNLOAD then
                      if goupdatestatus == nil then
                        sampAddChatMessage((prefix..'Обновление прошло неудачно. Запускаю устаревшую версию..'), color)
                        update = false
                      end
                    end
                  end
                )
                end, prefix
              )
            else
              update = false
              print('v'..thisScript().version..': Обновление не требуется.')
            end
          end
        else
          print('v'..thisScript().version..': Не могу проверить обновление. Смиритесь или проверьте самостоятельно на '..url)
          update = false
        end
      end
    end
  )
  while update ~= false do wait(100) end
end

function ClearChat()
    local memory = require "memory"
    memory.fill(sampGetChatInfoPtr() + 306, 0x0, 25200)
    memory.write(sampGetChatInfoPtr() + 306, 25562, 4, 0x0)
    memory.write(sampGetChatInfoPtr() + 0x63DA, 1, 1)
end
1. Вызов функции сделай перед sampRegisterChatCommand.
2. Ссылка на скачивание скрипта должна быть прямой. Гугл диск не подойдёт для этих целей.

если изменить версию в github то обновляется ссылка, как без изменение ссылки можно редактировать gist
У меня тоже не получилось. Пользуйся сниппетом гитлаба или другими способами.
UPD. На самом деле можно.
Вместо ссылки https://gist.githubusercontent.com/...raw/7925d0f4a9ce119cc22a64c06ad8140f2fe4a6c7/, нужно пользоваться https://gist.githubusercontent.com/qrlk/5d1540c87b9c585389504ba7124bc47d/raw/
 
  • Нравится
Реакции: atizoff

Petr_Sergeevich

Известный
Проверенный
707
297
Можно через dropbox делать ссылки для загрузки, они там не изменяются из-за редактирования
 
  • Нравится
Реакции: atizoff