view src/bbcode/Bbcode.luan @ 53:cac477dd1f82

convert image and video urls
author Franklin Schmidt <fschmidt@gmail.com>
date Thu, 24 Nov 2022 22:54:43 -0700
parents f225e82b2bf8
children
line wrap: on
line source

local Luan = require "luan:Luan.luan"
local error = Luan.error
local type = Luan.type or error()
local ipairs = Luan.ipairs or error()
local pairs = Luan.pairs or error()
local stringify = Luan.stringify or error()
local Io = require "luan:Io.luan"
local output_of = Io.output_of or error()
local Parsers = require "luan:Parsers.luan"
local bbcode_parse = Parsers.bbcode_parse or error()
local Html = require "luan:Html.luan"
local html_encode = Html.encode or error()
local html_parse = Html.parse or error()
local Table = require "luan:Table.luan"
local is_list = Table.is_list or error()
local concat = Table.concat or error()
local String = require "luan:String.luan"
local regex = String.regex or error()
local ends_with = String.ends_with or error()
local User = require "site:/lib/User.luan"
local Shared = require "site:/lib/Shared.luan"
local list_to_set = Shared.list_to_set or error()
local to_list = Shared.to_list or error()
local Logging = require "luan:logging/Logging.luan"
local logger = Logging.logger "Bbcode"


local Bbcode = {}

local starting_cr_regex = regex[[^\n]]

local to_html
local html = {}

function html.b(bbcode,options)
	%><b><% to_html(bbcode.contents,options) %></b><%
end

function html.i(bbcode,options)
	%><i><% to_html(bbcode.contents,options) %></i><%
end

function html.u(bbcode,options)
	%><u><% to_html(bbcode.contents,options) %></u><%
end

function html.s(bbcode,options)
	%><s><% to_html(bbcode.contents,options) %></s><%
end

function html.sup(bbcode,options)
	%><sup><% to_html(bbcode.contents,options) %></sup><%
end

function html.sub(bbcode,options)
	%><sub><% to_html(bbcode.contents,options) %></sub><%
end

function html.brackets(bbcode,options)
	%>[<% to_html(bbcode.contents,options) %>]<%
end

function html.url(bbcode,options)
	local url = bbcode.param
	if url == nil then
		url = html_encode(bbcode.contents)
		%><a href="<%=url%>"><%=url%></a><%
	else
		url = html_encode(url)
		%><a href="<%=url%>"><% to_html(bbcode.contents,options) %></a><%
	end
end

function html.code(bbcode,options)
	if bbcode.param == "inline" then
		%><code inline><%= html_encode(bbcode.contents) %></code><%
	else
		local s = starting_cr_regex.gsub(bbcode.contents,"")
		%><code><%= html_encode(s) %></code><%
		options.strip_newline = true
	end
end

function html.img(bbcode,options)
	%><img src="<%= html_encode(bbcode.contents) %>"<%
	if bbcode.param ~= nil then
		%> width="<%=bbcode.param%>"<%
	end
	%>><%
end

function html.color(bbcode,options)
	%><span style="color:<%=bbcode.param%>"><% to_html(bbcode.contents,options) %></span><%
end

function html.size(bbcode,options)
	%><span style="font-size:<%=bbcode.param%>"><% to_html(bbcode.contents,options) %></span><%
end

function html.quote(bbcode,options)
	%><blockquote><%
	local user_name = bbcode.param
	if user_name ~= nil then
		local user = User.get_by_name(user_name)
		if user == nil then
			%><%= user_name %> wrote:<%
		else
			%><a href="/user_something"><%= user_name %></a> wrote:<%
		end
	else
		options.strip_newline = true
	end
	to_html(bbcode.contents,options)
	%></blockquote><%
	options.strip_newline = true
end

local function video_iframe(url)
	%><iframe width="560" height="315" frameborder="0" allowfullscreen src="<%=url%>"></iframe><%
end

local video_urls = {
	[[\.mp4$]]
}
local video_handlers = {}
local function regex_video(s)
	video_urls[#video_urls+1] = s
	return regex(s)
end
do
	local ptn1 = regex_video[[^\Qhttps://youtu.be/\E([a-zA-Z0-9_-]+)(?:\?t=([0-9]+))?]]
	local ptn2 = regex_video[[^\Qhttps://www.youtube.com/watch?v=\E([a-zA-Z0-9_-]+)(?:&t=([0-9]+)s)?]]
	function video_handlers.youtube(url)
		local id, start = ptn1.match(url)
		if id == nil then
			id, start = ptn2.match(url)
		end
		if id == nil then
			return false
		end
		url = "https://www.youtube.com/embed/"..id
		if start ~= nil then
			url = url.."?start="..start
		end
		video_iframe(url)
		return true
	end
end
do
	local ptn = regex_video[[^\Qhttps://rumble.com/embed/\E[a-z0-9]+/\?pub=[a-z0-9]+]]
	function video_handlers.rumble(url)
		if not ptn.matches(url) then
			return false
		end
		video_iframe(url)
		return true
	end
end
do
	local ptn = regex_video[[^\Qhttps://www.bitchute.com/video/\E([a-zA-Z0-9]+)/]]
	function video_handlers.bitchute(url)
		local id = ptn.match(url)
		if id == nil then
			return false
		end
		url = "https://www.bitchute.com/embed/"..id.."/"
		video_iframe(url)
		return true
	end
end
do
	local ptn = regex_video[[^\Qhttps://vimeo.com/\E([0-9]+)]]
	function video_handlers.vimeo(url)
		local id = ptn.match(url)
		if id == nil then
			return false
		end
		url = "https://player.vimeo.com/video/"..id
		video_iframe(url)
		return true
	end
end
do
	local ptn = regex_video[[^\Qhttps://dai.ly/\E([a-z0-9]+)]]
	function video_handlers.dailymotion(url)
		local id = ptn.match(url)
		if id == nil then
			return false
		end
		url = "https://www.dailymotion.com/embed/video/"..id
		video_iframe(url)
		return true
	end
end
do
	local ptn = regex_video[[^\Qhttps://www.tiktok.com/\E[^/]+/video/([0-9]+)]]
	function video_handlers.tiktok(url)
		local id = ptn.match(url)
		if id == nil then
			return false
		end
		%><iframe allowfullscreen scrolling="no" width="325" height="720" src="https://www.tiktok.com/embed/<%=id%>"></iframe><%
		return true
	end
end
do
	local ptn = regex[[\.[a-zA-Z0-9]+$]]
	function video_handlers.file(url)
		if not ptn.matches(url) then
			return false
		end
		%><video controls width="560"><source src="<%=html_encode(url)%>"></video><%
		return true
	end
end

local video_regex = regex(concat(video_urls,"|"))

function html.video(bbcode,options)
	local url = bbcode.contents
	for _, handle in pairs(video_handlers) do
		if handle(url) then return end
	end
	url = html_encode(url)
	%><a href="<%=url%>"><%=url%></a><%
end

local function list_to_html(bbcode,options)
	local list = to_list(bbcode.contents)
	for _, item in ipairs(list) do
		if type(item) == "table" and item.name == "item" then
			%><li><% to_html(item.contents,options) %></li><%
		end
	end
	options.strip_newline = true
end

function html.list(bbcode,options)
	local tag = bbcode.param == "1" and "ol" or "ul"
	%><<%=tag%>><%
	list_to_html(bbcode,options)
	%></<%=tag%>><%
end

function to_html(bbcode,options)
	if options.strip_newline then
		if type(bbcode) == "string" then
			bbcode = starting_cr_regex.gsub(bbcode,"")
		end
		options.strip_newline = false
	end
	if type(bbcode) == "string" then
		%><%= html_encode(bbcode) %><%
	else
		type(bbcode) == "table" or error()
		if is_list(bbcode) then
			for _, v in ipairs(bbcode) do
				to_html(v,options)
			end
		else
			local fn = html[bbcode.name] or error(bbcode.name.." not handled")
			fn(bbcode,options)
		end
	end
end

function Bbcode.to_html(bbcode)
	bbcode = bbcode_parse(bbcode)
	%><div from_bbcode><%
	to_html(bbcode,{strip_newline=false})
	%></div><%
end


local doesnt_nest = list_to_set{
	"url"
	"code"
	"img"
	"video"
}

local url_regex = regex[[(^|\s)(https?://\S+)]]
local img_regex
do
	local endings = {
		"jpg"
		"jpeg"
		"png"
		"gif"
		"svg"
	}
	local t = {}
	for _, ending in ipairs(endings) do
		t[#t+1] = [[\.]]..ending..[[$]]
	end
	img_regex = regex(concat(t,"|"))
end

local function preprocess(bbcode)
	if type(bbcode) == "string" then
		bbcode = url_regex.gsub( bbcode, function(prefix,url)
			if video_regex.matches(url) then
				return prefix.."[video]"..url.."[/video]"
			elseif img_regex.matches(url) then
				return prefix.."[img]"..url.."[/img]"
			else
				return prefix.."[url]"..url.."[/url]"
			end
		end )
		%><%= bbcode %><%
	else
		type(bbcode) == "table" or error()
		if is_list(bbcode) then
			for _, v in ipairs(bbcode) do
				preprocess(v)
			end
		else
			local name = bbcode.name
			local param = bbcode.param
			%>[<%=name%><%
			if param ~= nil then
				%>=<%=param%><%
			end
			%>]<%
			if doesnt_nest[name] then
				%><%=bbcode.contents%><%
			else
				preprocess(bbcode.contents)
			end
			if name == "code" and param ~= nil then
				%>[/<%=name%>=<%=param%>]<%
			else
				%>[/<%=name%>]<%
			end
		end
	end
end

-- url handling
function Bbcode.preprocess(bbcode)
	bbcode = bbcode_parse(bbcode)
	return output_of(function()
		preprocess(bbcode)
	end)
end

function Bbcode.remove_html(text)
	local ends_with_newline = ends_with(text,"\n")
	local t = {}
	local html = html_parse(text)
	local is_first = true
	for _, v in ipairs(html) do
		if type(v) == "string" then
			t[#t+1] = v
		else
			local name = v.name
			if name == "div" then
				if not is_first then
					t[#t+1] = "\n"
				end
			elseif name == "/div" or name == "br" then
				-- ignore
			else
				error("unexpected tag: "..name)
			end
		end
		is_first = false
	end
	if not ends_with_newline then
		t[#t+1] = "\n"
	end
	return concat(t)
end


return Bbcode