310 lines
7.2 KiB
Lua
310 lines
7.2 KiB
Lua
local util = {}
|
|
local magick = require("magick")
|
|
local db = require("lapis.db")
|
|
local html_escape = require("lapis.html").escape
|
|
local constants = require("constants")
|
|
local string_trim = require("lapis.util").trim
|
|
|
|
local Avatars = require("models").Avatars
|
|
local Users = require("models").Users
|
|
local Posts = require("models").Posts
|
|
local PostHistory = require("models").PostHistory
|
|
|
|
local babycode = require("lib.babycode")
|
|
|
|
util.TransientUser = {
|
|
is_admin = function (self)
|
|
return false
|
|
end,
|
|
is_mod = function (self)
|
|
return false
|
|
end,
|
|
is_guest = function (self)
|
|
return true
|
|
end,
|
|
is_system = function (self)
|
|
return false
|
|
end,
|
|
is_logged_in_guest = function (self)
|
|
return false
|
|
end,
|
|
is_logged_in = function (self)
|
|
return false
|
|
end,
|
|
username = "Deleted User",
|
|
}
|
|
|
|
-- PURE API
|
|
|
|
function util.get_user_avatar_url(req, user)
|
|
return Avatars:find(user.avatar_id).file_path
|
|
end
|
|
|
|
---split a string
|
|
---@param s string subject
|
|
---@param delimiter string? string to split by, can be empty to split by character
|
|
---@param max_matches integer? the maximum number of returned elements
|
|
---@param trim boolean? whether to trim whitespace off matches
|
|
---@param allow_empty boolean? should empty matches be in the resulting table
|
|
---@return string[]
|
|
function util.s_split(s, delimiter, max_matches, trim, allow_empty)
|
|
local result = {}
|
|
if s == "" then
|
|
return result
|
|
end
|
|
trim = trim == nil and true or trim
|
|
local tr = function(subj)
|
|
if trim then return string_trim(subj) else return subj end
|
|
end
|
|
max_matches = max_matches or -1
|
|
allow_empty = allow_empty == nil and true or allow_empty
|
|
|
|
if delimiter == "" then
|
|
for i=1, #s do
|
|
local c = s:sub(i, 1)
|
|
if allow_empty or c ~= "" then
|
|
table.insert(result, c)
|
|
if max_matches > 0 and #result == max_matches then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
return result
|
|
end
|
|
|
|
local current_pos = 1
|
|
local delim_len = #delimiter
|
|
|
|
while true do
|
|
if max_matches > 0 and #result >= max_matches then
|
|
break
|
|
end
|
|
---@diagnostic disable-next-line: param-type-mismatch
|
|
local start_pos, end_pos = s:find(delimiter, current_pos, true)
|
|
if not start_pos then
|
|
break
|
|
end
|
|
local substr = s:sub(current_pos, start_pos - 1)
|
|
if allow_empty or substr ~= "" then
|
|
table.insert(result, tr(substr))
|
|
end
|
|
current_pos = end_pos + 1
|
|
end
|
|
|
|
local substr = s:sub(current_pos)
|
|
if allow_empty or substr ~= "" then
|
|
table.insert(result, tr(substr))
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
function util.split_sentences(sentences, max_sentences)
|
|
return util.s_split(sentences, ".", max_sentences or 2, true, false)
|
|
end
|
|
|
|
function util.infobox_message(msg)
|
|
local sentences = util.split_sentences(msg)
|
|
if #sentences == 1 then
|
|
return "<b>" .. sentences[1] .. ". " .. "</b>"
|
|
end
|
|
return "<span><b>" .. sentences[1] .. ". " .. "</b> " .. sentences[2] .. ".</span>"
|
|
end
|
|
|
|
function util.get_logged_in_user(req)
|
|
if req.session.session_key == nil then
|
|
return nil
|
|
end
|
|
|
|
local session = db.select('* FROM "sessions" WHERE "key" = ? AND "expires_at" > "?" LIMIT 1', req.session.session_key, os.time())
|
|
if #session > 0 then
|
|
return Users:find({id = session[1].user_id})
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
function util.get_logged_in_user_or_transient(req)
|
|
return util.get_logged_in_user(req) or util.TransientUser
|
|
end
|
|
|
|
function util.ntob(v)
|
|
return v ~= 0
|
|
end
|
|
|
|
function util.bton(b)
|
|
return 1 and b or 0
|
|
end
|
|
|
|
function util.stob(s)
|
|
if s == "true" then
|
|
return true
|
|
end
|
|
if s == "false" then
|
|
return false
|
|
end
|
|
end
|
|
|
|
function util.form_bool_to_sqlite(s)
|
|
return util.bton(util.stob(s))
|
|
end
|
|
|
|
function util.is_thread_locked(thread)
|
|
return util.ntob(thread.is_locked)
|
|
end
|
|
|
|
function util.is_topic_locked(topic)
|
|
return util.ntob(topic.is_locked)
|
|
end
|
|
|
|
-- OTHER API
|
|
|
|
function util.validate_and_create_image(input_image, filename)
|
|
local img = magick.load_image_from_blob(input_image)
|
|
|
|
if not img then
|
|
return false
|
|
end
|
|
|
|
img:strip()
|
|
img:set_gravity("CenterGravity")
|
|
|
|
local width, height = img:get_width(), img:get_height()
|
|
local min_dim = math.min(width, height)
|
|
if min_dim > 256 then
|
|
local ratio = 256.0 / min_dim
|
|
local new_w, new_h = width * ratio, height * ratio
|
|
img:resize(new_w, new_h)
|
|
end
|
|
|
|
width, height = img:get_width(), img:get_height()
|
|
local crop_size = math.min(width, height)
|
|
local x_offset = (width - crop_size) / 2
|
|
local y_offset = (height - crop_size) / 2
|
|
img:crop(crop_size, crop_size, x_offset, y_offset)
|
|
|
|
img:set_format("webp")
|
|
img:set_quality(85)
|
|
|
|
img:write(filename)
|
|
img:destroy()
|
|
return true
|
|
end
|
|
|
|
function util.destroy_avatar(avatar_id)
|
|
if avatar_id == 1 then
|
|
print("won't delete default avatar")
|
|
return
|
|
end
|
|
|
|
local avatar = Avatars:find(avatar_id)
|
|
|
|
if not avatar then
|
|
return
|
|
end
|
|
|
|
local file_path = "static" .. avatar.file_path
|
|
local f = io.open(file_path, "r")
|
|
if not f then
|
|
print("can't open avatar file")
|
|
else
|
|
f:close()
|
|
os.remove(file_path)
|
|
avatar:delete()
|
|
end
|
|
end
|
|
|
|
function util.create_post(thread_id, user_id, content, markup_language)
|
|
markup_language = markup_language or "babycode"
|
|
db.query("BEGIN")
|
|
local post = Posts:create({
|
|
thread_id = thread_id,
|
|
user_id = user_id,
|
|
current_revision_id = db.NULL,
|
|
})
|
|
|
|
local parsed_content = ""
|
|
if markup_language == "babycode" then
|
|
parsed_content = babycode.to_html(content, html_escape)
|
|
end
|
|
|
|
local revision = PostHistory:create({
|
|
post_id = post.id,
|
|
content = parsed_content,
|
|
is_initial_revision = true,
|
|
original_markup = content,
|
|
markup_language = "babycode",
|
|
})
|
|
|
|
post:update({current_revision_id = revision.id})
|
|
|
|
db.query("COMMIT")
|
|
return post
|
|
end
|
|
|
|
function util.update_post(post, new_content, markup_language)
|
|
markup_language = markup_language or "babycode"
|
|
db.query("BEGIN")
|
|
|
|
local parsed_content = ""
|
|
if markup_language == "babycode" then
|
|
parsed_content = babycode.to_html(new_content, html_escape)
|
|
end
|
|
|
|
local revision = PostHistory:create({
|
|
post_id = post.id,
|
|
content = parsed_content,
|
|
is_initial_revision = false,
|
|
original_markup = new_content,
|
|
markup_language = markup_language
|
|
})
|
|
|
|
post:update({current_revision_id = revision.id})
|
|
db.query("COMMIT")
|
|
end
|
|
|
|
function util.transfer_and_delete_user(user)
|
|
local deleted_user = Users:find({
|
|
username = "DeletedUser",
|
|
})
|
|
-- this needs to be atomic
|
|
db.query("BEGIN")
|
|
db.query('UPDATE "threads" SET "user_id" = ? WHERE "user_id" = ?', deleted_user.id, user.id)
|
|
db.query('UPDATE "posts" SET "user_id" = ? WHERE "user_id" = ?', deleted_user.id, user.id)
|
|
user:delete() -- uncomment later
|
|
db.query("COMMIT")
|
|
end
|
|
|
|
function util.pop_infobox(req)
|
|
if not req.session.infobox then return end
|
|
req.infobox = req.session.infobox
|
|
req.session.infobox = nil
|
|
end
|
|
|
|
function util.inject_infobox(req, message, kind)
|
|
kind = kind or constants.InfoboxKind.INFO
|
|
local ib = {
|
|
msg = message,
|
|
kind = kind,
|
|
}
|
|
req.session.infobox = ib
|
|
end
|
|
|
|
function util.inject_err_infobox(req, message)
|
|
local ib = {
|
|
msg = message,
|
|
kind = constants.InfoboxKind.ERROR,
|
|
}
|
|
req.session.infobox = ib
|
|
end
|
|
|
|
function util.inject_warn_infobox(req, message)
|
|
local ib = {
|
|
msg = message,
|
|
kind = constants.InfoboxKind.WARN,
|
|
}
|
|
req.session.infobox = ib
|
|
end
|
|
|
|
return util
|