improved rssatom/federation/peertube plugins + ttemplates

This commit is contained in:
Leon van Kammen 2026-08-17 13:42:27 +02:00
parent abc9caedb2
commit 1f4b3d52ec
47 changed files with 553 additions and 237 deletions

View file

@ -1,7 +1,8 @@
XRForge is a tiny, powerful tool to interconnect XR experiences via 3D portals...like a federated viverse.com<br>
<br>
An open, seamless XR web built from simple URLs and directories, without gatekeepers.<br>
An open, seamless XR web built from simple URLs and directories, without gatekeepers, built upon Next Generation Internet (NGI) specs:<br>
<br>
<img src="https://codeberg.org/coderofsalvation/xrforge-v2/raw/branch/master/src/img/badges.png"/>
<br>
<a href="screenshot.jpg" target="_blank">
<img src="screenshot.jpg"/>

View file

@ -14,8 +14,6 @@ HidePath('/usr/share/ssl/')
-- create
app = require("soakbean") {
bin = "./xrforge.com",
bindir = arg[-1]:gsub("/[a-zA-Z0-9-_ ]+.com$","/"),
cache = { items = {}, url = {} },
plugin = {},
opts = {},
@ -62,11 +60,13 @@ app.plugin.moderation = app.addPlugin('plugin/moderation')
app.plugin.xrfragments = app.addPlugin('plugin/xrfragments')
app.plugin.janusxr = app.addPlugin('plugin/janusxr')
app.plugin.clouddrive = app.addPlugin('plugin/clouddrive')
app.plugin.markdown = app.addPlugin('plugin/markdown')
app.plugin.rssatom = app.addPlugin('plugin/rssatom')
app.plugin.federation = app.addPlugin('plugin/federation')
app.plugin.manyfold = app.addPlugin('plugin/manyfold')
app.plugin.peertube = app.addPlugin('plugin/peertube')
app.plugin.git = app.addPlugin('plugin/git')
app.plugin.webhook = app.addPlugin('plugin/webhook')
app.plugin.markdown = app.addPlugin('plugin/markdown')
app.plugin.staticwebgen = app.addPlugin('plugin/staticwebgen') -- should always be last
app.runcmd(app) -- run clicmds
@ -92,7 +92,11 @@ app.post('^/save', function(req,res,next) -- setup inline POST endpoint
end)
app --
.use( require("middleware/httpauth")() )
.use( require("middleware/httpauth")({
user = app.opts.admin_user,
pass = app.opts.admin_pass,
path = "^/admin"
}))
.use( require("json").middleware() ) -- try plug'n'play json API middleware
.use( require('page/dir').middleware ) -- directory browser
.use( app.router( app.url ) ) -- try url router

View file

@ -7,6 +7,8 @@ return function(app)
baseurl="/",
admin_title = "admin panel",
admin_subtitle = "Manage your XR experiences here",
admin_user = "admin",
admin_pass = "admin"
}
local configfile = "xrforge.lua"

View file

@ -181,7 +181,8 @@ sb = {
run = function(req)
local next = function() end
local k = 0
local req = {
local internal = req
req = req or {
param={},
method=GetMethod(),
host=GetHost(),
@ -195,7 +196,7 @@ sb = {
_header={},
_body=""
}
local params = GetParams()
local params = internal and {} or GetParams()
if params ~= nil then
for i,p in pairs(params) do req.param[ p[1] ] = p[2] end
end
@ -238,6 +239,7 @@ sb = {
response = function()
return function(req,res,next)
sb.pub('response', {req=req, res=res, next=next})
if res._status ~= nil then
SetStatus(res._status)
for k,v in pairs(res._header) do
@ -293,7 +295,7 @@ sb = {
end
print("\nUsage: " .. app.bin .. " <cmd> [opts]\n\n")
for k,v in pairs(app.cmd) do
print("\t" .. app.bin .. " " .. k .. "\t\t" .. v.info )
print( string.format("\t%s %-15s %s", app.bin, k, v.info ) )
end
print("")
os.exit()
@ -304,6 +306,8 @@ sb = {
return function(data)
local app = {}
setmetatable(app,sb)
sb.bin = arg[-1]:gsub(".*/","")
sb.bindir = arg[-1]:gsub("/[a-zA-Z0-9-_ ]+.com$","/")
sb.app = app
sb.data = data
sb.data.url = {}

View file

@ -96,6 +96,8 @@ util.template = function(content,data)
mdata.url = mdata.url or util.getURL()
mdata.title = mdata.title or app.opts.subtitle
mdata.header_include = mdata.header_include or ''
mdata.footerlinks = {}
app.pub('util.template', nil, mdata)
mdata.header = app.tpl( LoadAsset('page/header.html'), mdata )
if not mdata.janusxr then
mdata.janusxr = app.tpl( LoadAsset('page/janusxr.html'), mdata )
@ -328,5 +330,21 @@ function util.stripPath(str)
return str:gsub("^./",""):gsub("^/",""):gsub("/$","")
end
-- WARNING: this is a destructive function which renders the redbean server inactive
-- only to be used in tests or staticwebgen plugin
util.queueServerRequest = function(method, path, params, host, headers, body, scheme, port)
GetMethod = function() return method end
GetPath = function() return path end
GetParams = function() return params or {} end -- note: { {"key","value"} } format
GetHost = function() return host or 'localhost' end
GetHeaders = function() return headers or {} end
GetScheme = function() return scheme or 'https' end
GetBody = function() return body or '' end
GetPort = function() return port or 80 end
SetStatus = function() end
Write = function() end
SetHeader = function() end
Route = function() end
end
return util

View file

@ -2,6 +2,4 @@ return function(app,argv)
print('generating..')
-- do nothing here, as this is handled by the src/plugin/staticwebgen.lua
-- AFTER all plugins are loaded
print('TODO: disabled for now..not stable')
os.exit()
end

View file

@ -7,7 +7,7 @@ return function(app,argv)
io.write("Flags:\n\n")
foreach( app.opts, function(k,v)
if not k:match("/") and not k:match("^help") and not k:match("updated") then
io.write( " " .. k .. "='" .. v .. "'\n")
io.write( " " .. k .. "='" .. v:gsub("\n","") .. "'\n")
end
end)
io.write("\n> NOTE: flags override flags in xrforge.lua configfile]\n")

View file

@ -4,6 +4,7 @@ return function(app,argv)
print("starting server")
if not util.contains(arg,"--nobrowser") then
if arg[1] == 'admin' then
print(" admin login/pass is " .. app.opts.admin_user .. "/" .. app.opts.admin_pass)
LaunchBrowser('/admin')
else
LaunchBrowser('/')

View file

@ -54,11 +54,15 @@ body{
font-size:14px;
}
img.icon.invert,
img.icon.link,
img.icon.play,
img.icon.download,
img.icon.list-tree{
filter: invert(1);
}
img.icon.play{
filter: invert(1);
img.icon.download,
img.icon.play,{
transform: translate(0,-1px);
}

16
src/img/download.svg Normal file
View file

@ -0,0 +1,16 @@
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11 5C11 4.44772 11.4477 4 12 4C12.5523 4 13 4.44772 13 5V12.1578L16.2428 8.91501L17.657 10.3292L12.0001 15.9861L6.34326 10.3292L7.75748 8.91501L11 12.1575V5Z"
fill="currentColor"
/>
<path
d="M4 14H6V18H18V14H20V18C20 19.1046 19.1046 20 18 20H6C4.89543 20 4 19.1046 4 18V14Z"
fill="currentColor"
/>
</svg>

After

Width:  |  Height:  |  Size: 445 B

22
src/img/drive.svg Normal file
View file

@ -0,0 +1,22 @@
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 11C18.4477 11 18 11.4477 18 12C18 12.5523 18.4477 13 19 13C19.5523 13 20 12.5523 20 12C20 11.4477 19.5523 11 19 11Z"
fill="currentColor"
/>
<path
d="M14 12C14 11.4477 14.4477 11 15 11C15.5523 11 16 11.4477 16 12C16 12.5523 15.5523 13 15 13C14.4477 13 14 12.5523 14 12Z"
fill="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M2 8C0.895431 8 0 8.89543 0 10V14C0 15.1046 0.89543 16 2 16H22C23.1046 16 24 15.1046 24 14V10C24 8.89543 23.1046 8 22 8H2ZM22 10H2L2 14H22V10Z"
fill="currentColor"
/>
</svg>

After

Width:  |  Height:  |  Size: 679 B

20
src/img/link.svg Normal file
View file

@ -0,0 +1,20 @@
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.8284 12L16.2426 13.4142L19.071 10.5858C20.6331 9.02365 20.6331 6.49099 19.071 4.9289C17.509 3.3668 14.9763 3.3668 13.4142 4.9289L10.5858 7.75732L12 9.17154L14.8284 6.34311C15.6095 5.56206 16.8758 5.56206 17.6568 6.34311C18.4379 7.12416 18.4379 8.39049 17.6568 9.17154L14.8284 12Z"
fill="currentColor"
/>
<path
d="M12 14.8285L13.4142 16.2427L10.5858 19.0711C9.02372 20.6332 6.49106 20.6332 4.92896 19.0711C3.36686 17.509 3.36686 14.9764 4.92896 13.4143L7.75739 10.5858L9.1716 12L6.34317 14.8285C5.56212 15.6095 5.56212 16.8758 6.34317 17.6569C7.12422 18.4379 8.39055 18.4379 9.1716 17.6569L12 14.8285Z"
fill="currentColor"
/>
<path
d="M14.8285 10.5857C15.219 10.1952 15.219 9.56199 14.8285 9.17147C14.4379 8.78094 13.8048 8.78094 13.4142 9.17147L9.1716 13.4141C8.78107 13.8046 8.78107 14.4378 9.1716 14.8283C9.56212 15.2188 10.1953 15.2188 10.5858 14.8283L14.8285 10.5857Z"
fill="currentColor"
/>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -1,5 +0,0 @@
<title>world 1 title</title>
<fireboxroom>
<room use_local_asset="room2">
</room>
</fireboxroom>

View file

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View file

@ -0,0 +1,6 @@
# AFRAME hello world
[XRForge's](https://xrforge.isvery.ninja/) [JanusXR](https://coderofsalvation.github.io/janus-guide/)-viewer ([janusweb](https://github.com/jbaicoianu/janusweb)) can detect [AFRAME](https://aframe.io) markup in HTML/XML/even binary files(!).
This basically extends [the seamless JanusXR portalsystem](https://coderofsalvation.github.io/janus-guide/#/home/about) by <b>interconnecting</b> otherwise immersively-disjointed ecosystems like [AFRAME](https://aframe.io), by [reprojecting them in 3D](https://coderofsalvation.github.io/janus-guide/#/home/about).
Currently only geometry markup and models are supported.

View file

@ -0,0 +1,12 @@
<a-scene>
<a-assets>
<a-asset-item id="tree" src="tree2.glb"></a-asset-item>
</a-assets>
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
<a-plane position="0 0 -4" rotation="-90 0 0" width="7" height="7" color="#7BC8A4"></a-plane>
<a-gltf-model src="#tree" position="-2 0.05 -5.5" scale="2 2 2"></a-gltf-model>
</a-scene>

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View file

@ -0,0 +1,5 @@
# JanusXR JML
[XRForge's](https://xrforge.isvery.ninja/) uses a [JanusXR](https://coderofsalvation.github.io/janus-guide/)-viewer called ([janusweb](https://github.com/jbaicoianu/janusweb)).
> JanusWeb supports various URLs, containing [JML](https://coderofsalvation.github.io/janus-guide/#/build/room), [XRFragments](https://xrfragment.org), RSS/Atom, HTML, and so on..basically an <b>in-browser VR browser</b>

View file

@ -0,0 +1,10 @@
<title>JanusXR hello world</title>
<fireboxroom>
<room use_local_asset="room4">
<object id="cube" pos="-5 1 0" scale="2 2 2"/>
<link js_id="aframescene" shader_id="defaultportal" draw_glow="false" url="../AFRAME/index.html" pos="2 0 0 "/>
</room>
</fireboxroom>

View file

@ -1,7 +1,8 @@
# Tron World 1
This is a test
This is a glTF world which is only 65 kilobytes in size.<br>
Say what?<br>
<br>
> Fitting an entire virtual world into 65 KB is like trying to condense the whole Marvel Cinematic Universe into a single tweet. Its the digital equivalent of trying to build a full-scale, functioning Disneyland inside a hollowed-out peanut shell.
> This is a blockquote
And [this](https://xrfragment.org) is an URL.
Read more about [best practices at xrfragment.org](https://xrfragment.org/#Best%20practices)

Binary file not shown.

View file

@ -1,52 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE X3D PUBLIC "ISO//Web3D//DTD X3D 3.0//EN" "https://www.web3d.org/specifications/x3d-3.0.dtd">
<X3D version="3.0" profile="Immersive" xmlns:xsd="http://www.w3.org/2001/XMLSchema-instance" xsd:noNamespaceSchemaLocation="https://www.web3d.org/specifications/x3d-3.0.xsd">
<head>
<meta name="filename" content="test_world.x3d" />
<meta name="generator" content="Blender 2.78 (sub 0)" />
</head>
<Scene>
<NavigationInfo headlight="true"
visibilityLimit="0.0"
type='"EXAMINE", "ANY"'
avatarSize="0.25, 1.75, 0.75"
/>
<Background DEF="WO_World"
groundColor="0.051 0.051 0.051"
skyColor="0.051 0.051 0.051"
/>
<Transform DEF="Cube_TRANSFORM"
translation="0.000000 1.000000 0.000000"
scale="5.000000 5.000000 1.000000"
rotation="0.000000 0.707107 0.707107 3.141593"
>
<Transform DEF="Cube_ifs_TRANSFORM"
translation="0.000000 0.000000 0.000000"
scale="1.000000 1.000000 1.000000"
rotation="1.000000 0.000000 0.000000 0.000000"
>
<Group DEF="group_ME_Cube">
<Shape>
<Appearance>
<Material DEF="MA_Material"
diffuseColor="0.500 0.500 1.000"
specularColor="0.401 0.401 0.401"
emissiveColor="0.000 0.000 0.000"
ambientIntensity="0.333"
shininess="0.098"
transparency="0.000"
/>
</Appearance>
<IndexedFaceSet solid="true"
coordIndex="0 3 2 1 -1 4 5 6 7 -1 0 1 5 4 -1 1 2 6 5 -1 2 3 7 6 -1 3 0 4 7 -1 "
>
<Coordinate DEF="coords_ME_Cube"
point="1.000000 1.000000 -1.000000 1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 1.000000 -1.000000 1.000000 0.999999 4.000000 0.999999 -1.000001 4.000000 -1.000000 -1.000000 4.000000 -1.000000 1.000000 4.000000 "
/>
</IndexedFaceSet>
</Shape>
</Group>
</Transform>
</Transform>
</Scene>
</X3D>

Binary file not shown.

View file

@ -6,7 +6,7 @@ header h2{
font-weight: normal;
}
header{
background-image: linear-gradient(0deg,#0000,#101,#0000), url(/myverse/tronverse/tron/tron.png);
background-image: linear-gradient(0deg,#0000,#101,#0000), url(myverse/tronverse/tron/tron.png);
background-position-x: 0%;
background-position-y: 0%;
background-repeat: repeat;
@ -20,7 +20,7 @@ header{
div#hint{
float:right;
background: url(/img/blinkhint.svg);
background: url(img/blinkhint.svg);
height: 129px;
background-size: cover;
display:inline-block;
@ -28,7 +28,7 @@ div#hint{
}
div#badges{
background: url(/img/badges.png);
background: url(img/badges.png);
height: 90px;
background-repeat:no-repeat;
background-size: contain;
@ -39,7 +39,7 @@ div#badges{
}
div#xrecosystem{
background: #000 url(/img/xrecosystem.png);
background: #000 url(img/xrecosystem.png);
background-repeat: no-repeat;
height: 200px;
background-size: cover;
@ -67,7 +67,7 @@ div#xrecosystem{
<br/>
<div class="btn">
<a href="https://codeberg.org/coderofsalvation/xrforge" class="" style="text-align:left">
<img src="/img/codeberg.svg" style="width:20px">
<img src="img/codeberg.svg" style="width:20px">
Check the sourcecode<br>
<small>docker run codeforge.org/coderofsalvation/xrforge:latest</small>
</a>
@ -79,7 +79,7 @@ div#xrecosystem{
</noscript>
<div class="btn">
<a href="https://chat.isvery.ninja" class="secondary" style="text-align:left">
<img src="/img/obsidian.png" style="width:20px">
<img src="img/obsidian.png" style="width:20px">
chat with community <br>
<small>irc.isvery.ninja port 443 via ObsidianIRC</small>
</a>
@ -347,7 +347,7 @@ div#xrecosystem{
<h3>JanusXR: The deep spatial web (within the web)</h3>
<div style="max-width:945px;">
<a href="https://coderofsalvation.github.io/janus-guide/" target="_blank">
<img src="/img/janusxr-xrf.png" style="width:100%"/>
<img src="img/janusxr-xrf.png" style="width:100%"/>
</a>
<br>
</div>

View file

@ -66,7 +66,7 @@ end)
function pluginUI(seen,data)
return function(name,plugin)
if plugin.hide or seen[ plugin.name ] then return end -- already processed
table.insert( data.tabs, plugin.name ) -- insert tabname
table.insert( data.tabs, plugin.tab or plugin.name ) -- insert tabname
seen[ plugin.name ] = true
local plugindata = {plugin=plugin}
plugindata.opts = ""
@ -88,6 +88,7 @@ function saveData()
end
end)
if saveconfig then
app.set('app.opts', app.opts)
data.notification = '<div class="notification">updated</div>'
else
data.notification = '<div class="notification">no changes detected</div>'

View file

@ -23,42 +23,38 @@ end}
local item = items[i]
local icon = 'list-tree'
local thumbnail = ''
if #item.thumbnail > 0 then thumbnail = 'style="background-image: url(' .. EscapeFragment(item.thumbnail) .. ');"' end
if #item.thumbnail > 0 then thumbnail = 'style="background-image: url(' .. EscapeFragment( util.stripPath(item.thumbnail) ) .. ');"' end
if item.view3D or item.portal3D == false then icon = 'play' end
print([[
<div class="tile">
<a href="]] .. item.url .. [[">
<div class="img" ]] .. thumbnail .. [[></div>
<a href="]] .. util.stripPath(item.url) .. [[">
<div class="img" ]] .. util.stripPath(thumbnail) .. [[></div>
<div class="text">
<img src="img/]] .. icon .. [[.svg" class="icon ]] .. icon .. [["/>&nbsp;<a href="]] .. item.url .. [[">]] .. item.title .. [[</a>
<img src="img/]] .. icon .. [[.svg" class="icon ]] .. icon .. [["/>&nbsp;<a href="]] .. util.stripPath(item.url) .. [[">]] .. item.title .. [[</a>
</div>
</a>
</div>
]])
end
end}
{if parent.parent == nil then
for i=1,5 do
local urls = app.opts[ 'plugin_federation_url_' .. i]
if #urls > 0 then
local title = urls:gsub(' .*','')
local fediurl = '/?url=' .. EscapeFragment(title)
local thumburl = urls:gsub('.* ','')
local icon = 'list-tree'
{if itemsremote then
for i=1,#itemsremote do
local item = itemsremote[i]
local icon = 'link'
local thumbnail = ''
if #thumburl > 0 then thumbnail = 'style="background-image: url(' .. EscapeFragment(thumburl) .. ');"' end
if #item.thumbnail > 0 then thumbnail = 'style="background-image: url(' .. EscapeFragment( util.stripPath(item.thumbnail) ) .. ');"' end
if item.view3D or item.portal3D == false then icon = 'play' end
print([[
<div class="tile">
<a href="]] .. fediurl .. [[">
<div class="img" ]] .. thumbnail .. [[></div>
<a href="]] .. util.stripPath(item.url) .. [[">
<div class="img" ]] .. util.stripPath(thumbnail) .. [[></div>
<div class="text">
<img src="img/]] .. icon .. [[.svg" class="icon ]] .. icon .. [["/>&nbsp;<a href="]] .. fediurl .. [[">]] .. title .. [[</a>
<img src="img/]] .. icon .. [[.svg" class="icon ]] .. icon .. [["/>&nbsp;<a href="]] .. util.stripPath(item.url) .. [[">]] .. item.title .. [[</a>
</div>
</a>
</div>
]])
end
end
end}
</div>

View file

@ -6,7 +6,8 @@ return {
middleware = function(req,res,next)
local rootpath = "/" .. app.opts.plugin_diskscan_dirname
local urlpath = GetPath()
local urlpath = req.url
local isroot = urlpath == "/" or urlpath:match("^"..rootpath.."[/]?$")
res.items = {}
res.template = res.template or 'page/dir.html'
local isdir = urlpath:sub(-1) == "/"
@ -14,9 +15,8 @@ return {
local match = function(a,b) return util.stripPath(a) == util.stripPath(b) end
local urlhash = urlpath .. util.dump( req.param )
if urlpath == "/"
or urlpath:match("^"..rootpath)
and not urlpath:gsub(".*/",""):match("%.")
if (isroot or urlpath:match("^"..rootpath))
and not urlpath:gsub(".*/",""):match("%.") -- no regular file with extension
and not req.param.url then
local redirect = false
@ -26,10 +26,10 @@ return {
foreach( app.cache.items, function(k,v)
if req.authenticated or app.plugin.moderation.isAllowed(v) then
if v.parent then
trace( "path = " .. util.stripPath(v.path) .. " " ..
"parent = " .. util.stripPath(v.parent.path) .. " " ..
"matchpath = " .. util.stripPath(matchpath)
)
-- trace( "path = " .. util.stripPath(v.path) .. " " ..
-- "parent = " .. util.stripPath(v.parent.path) .. " " ..
-- "matchpath = " .. util.stripPath(matchpath)
-- )
if match(v.path, matchpath) then -- show parent folder if any
local parent = app.cache.items[ v.parent.id ]
if v.portal then
@ -45,7 +45,6 @@ return {
-- add items from dir
if match(v.parent.path, matchpath) then
if v.dir or v.view3D then
trace("-- dir item: " .. v.path )
local parent = app.cache.items[ v.parent.id ]
res.items[ v.id ] = v
res.parent = parent
@ -57,32 +56,35 @@ return {
local data = util.merge({
url = req.param.url, -- query hint to render janus.url in iframe (index.html)
html_items='',
items = {},
parent=res.parent
},{ opts = app.opts })
local i = 0
foreach( res.items, function(k,v)
-- position items for janusxr JML (dir.html)
-- add extra metadata for janusxr JML (janusxr.html and dir.html)
table.insert(data.items, util.merge({x = i*2, i = i},v) )
i=i+1
if v.view3D then
local isMarkupExperience = function(v)
return v.janusxr and (v.janusxr.jml or v.janusxr.aframe)
end
-- prioritize markup experience above binary 3D files as experience containers
if not data.item or (isMarkupExperience(v) and not isMarkupExperience(data.item)) then
data.item = v
data.title = data.item.title
data.parent = v.parent
local port = ':' .. GetPort()
if port == 80 then port = '' end
data.url = GetScheme() .. '://' .. GetHost() .. port .. v.url
if v.janusxr and v.janusxr.jml then
data.url = app.opts.baseurl .. util.stripPath(v.url) -- because we are running in iframe
if v.janusxr and (v.janusxr.jml or v.janusxr.aframe) then
-- what to do here? no generated portals?
data.janusxr = app.tpl( util.readfile(v.path), data )
end
end
end
end)
if data.item then
res.template = 'page/index.html'
end
app.pub("dir", nil, {res=res,data=data, urlpath=urlpath, isdir=isdir})
app.pub("dir", nil, {res=res,data=data, urlpath=urlpath, isroot=isroot, isdir=isdir})
if not redirect then
app.cache.url[ urlhash ] = util.templateFile( res.template, data)
end

View file

@ -17,13 +17,20 @@
<a href="https://nlnet.nl">NLNET</a><br/>
</td>
<td class="col3">
<a href="/?view=3D">View from XR Device</a><br/>
<a href="/${opts.plugin_diskscan_dirname}">Content overview</a><br/>
<a href="/about">About</a><br/>
<a href="?view=3D">View from XR Device</a><br/>
<a href="${opts.plugin_diskscan_dirname}">Content overview</a><br/>
<a href="about">About</a><br/>
</td>
<td class="col3">
<a href="https://codeberg.org/coderofsalvation/xrforge">Sourcecode</a> <br>
<img src="/img/button-88x31.gif" style="display:inline-block"/>
<img src="img/button-88x31.gif" style="display:inline-block"/><br/>
<a href="https://codeberg.org/coderofsalvation/xrforge-v2">Sourcecode</a> <br>
{if footerlinks then
for i,v in pairs(footerlinks) do
print([[
<a href="]] .. v.url .. [[">]] .. v.title .. [[</a><br>
]])
end
end}
</td>
</tr>
</table>

View file

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<base href="${opts.baseurl}" />
<title>${opts.plugin_global_title}</title>
<link rel="stylesheet" href="/css/xrforge.css"></link>
<link rel="stylesheet" href="css/xrforge.css"></link>
${opts.plugin_global_headertags}
<noscript>
{if urlpath == '/' or urlpath == '/index.html' then
@ -33,7 +33,7 @@
<td class="menu">
<div class="margin1L">
<!-- important: no spaces between anchor tags for older browsers without flexbox -->
<a href="/${opts.plugin_diskscan_dirname}"><img class="icon" src="/img/list-tree.svg">${opts.plugin_global_topmenu_mapname}</a>&nbsp;
<a href="${opts.plugin_diskscan_dirname}"><img class="icon" src="img/list-tree.svg">${opts.plugin_global_topmenu_mapname}</a>&nbsp;
{if true then
print( app.opts.plugin_global_topmenu_html:gsub("\n","&nbsp;") )
end}

View file

@ -3,7 +3,7 @@ ${header}
<div></div>
</div>
<iframe border="0" frameborder="0" src="/janusweb/index.html#janus.url=${url}" id="viewer"></iframe>
<iframe border="0" frameborder="0" src="janusweb/index.html#janus.url=${url}" id="viewer"></iframe>
{if item and item.parent and item.parent.info then
print([[
@ -14,7 +14,7 @@ ${header}
<noscript>
<img src="${item.parent.thumbnail}" width="100%"/>
</noscript>
<img src="/img/close.svg" width="20" class="icon close" onclick="document.querySelector('#popup').style.display = 'none'"/>
<img src="img/close.svg" width="20" class="icon close" onclick="document.querySelector('#popup').style.display = 'none'"/>
]] .. item.parent.info .. [[
</div>
]])
@ -31,9 +31,14 @@ end}
if( url == document.location.href ) return
try{ // handle external urls via ?url=
let urlObj = new URL(url)
if( document.location.origin != urlObj.origin ){
url = document.location.origin + document.location.pathname + '?url=' + url
}
}catch(e){}
const isFileWithExtension = url.replace( new RegExp('./','g'),"").match("\\.")
if( !isFileWithExtension ){
window.history.replaceState( url, iframe.contentDocument.title, url );
}
setTimeout( () => {
document.title = iframe.contentDocument.title
}, 1000 )

View file

@ -14,6 +14,13 @@ end}
end
end
end}
{if itemsremote and #itemsremote > 0 then
for i,v in pairs(itemsremote) do
if #v.thumbnail > 0 then
print('\t<assetimage src="' .. v.thumbnail .. '" id="imgremote' .. i .. '"/>')
end
end
end}
{if showfediportals then
for i=1,5 do
local urls = app.opts[ 'plugin_federation_url_' .. i]
@ -49,9 +56,6 @@ end}
end}
<text js_id="location" pos="0 6 -5" billboard="y" col="#BBFFFF" scale="10 10 1" text="${url}" emissive="0 0 0" roughness="0.3" metalness="0" />
{if not item and items and #items > 0 then
local portals = {}
for i,v in pairs(items) do table.insert(portals, v) end
if parent then table.insert(portals, p) end
for i,v in pairs(items) do
local width = 3.2
local center = -((#items*width)/1.3) + (i*width)
@ -65,27 +69,24 @@ end}
addPortal('dir',center, v, url,i, -4, thumb, '0 0 0', '0.2 0.2 0.2')
end
end}
{if showfediportals and not item then
local portals = {}
for i=1,5 do
local urls = app.opts[ 'plugin_federation_url_' .. i]
if #urls > 0 then
local fediurl = urls:gsub(' .*','')
table.insert(portals, fediurl )
end
end
if #portals > 0 then
print([[
<text js_id="location" pos="0 6 5" billboard="y" col="#BBFFFF" scale="10 10 1" text="${opts.plugin_federation_title}" emissive="0 0 0" roughness="0.3" metalness="0" />
]])
end
for i,fediurl in pairs(portals) do
{if not item and itemsremote and #itemsremote > 0 then
for i,v in pairs(itemsremote) do
local width = 3.2
local center = -((#portals*width)/1.3) + (i*width)
local thumb = ' thumb_id="federated' .. i .. '"'
addPortal('federated',center, {title=fediurl}, fediurl,i, 4, thumb, '0 180 0', '0.1 0.1 0.1')
local center = -((#itemsremote*width)/1.3) + (i*width)
local url = v.url
local thumb = ' shader_id="defaultportal"'
if #v.thumbnail > 0 then
thumb = ' thumb_id="imgremote' .. i .. '" '
end
if v.dir then url = url .. "/" end
if v.redirect then url = v.redirect end -- dont wrap standalone glb's in janusxr
addPortal('remote',center, v, url,i, 4, thumb, '0 180 0', '0.2 0.2 0.2')
end
print([[
<text js_id="locationremote" pos="0 6 5" billboard="y" col="#BBFFFF" scale="10 10 1" text="${opts.plugin_global_remote_title}" emissive="0 0 0" roughness="0.3" metalness="0" />
]])
end}
<object id="cube" rotation="0 0 0" position="0 0 0" scale="2 0.1 2"/> <!-- something to stand on -->
${opts.plugin_janusxr_jml}
</room>

View file

@ -8,7 +8,8 @@ end)
-- this registers the plugin UI /funcs
return {
name = "Local Content",
name = "Files",
tab = "<img src='img/drive.svg' class='icon invert'/> Files",
info = '',
opts = {
{

View file

@ -11,70 +11,70 @@ app.on('set', function(k,v)
end)
app.on('dir', function(opts)
local me = app.plugin.federation
if opts.urlpath == '/' then
print_r(me.portals)
--foreach( me.portals, function(k,portal)
-- print("JAAA")
-- table.insert( opts.data.items,{
-- url = portal.url,
-- name = portal.name,
-- thumbnail = portal.thumbnail
-- })
--end)
local showportals = app.opts.plugin_federation_strategy == 'always' or
(app.opts.plugin_federation_strategy == 'homepage' and opts.isroot)
if showportals then
opts.data.itemsremote = opts.data.itemsremote or {}
local itemsremote = opts.data.itemsremote
for i=1,5 do
local urls = app.opts[ 'plugin_federation_url_' .. i]
if #urls > 0 then
local url = urls:gsub(' .*','')
local thumburl = urls:match(" ") and urls:gsub('.* ','') or nil
table.insert(itemsremote, {
title = url:gsub(".*//",""),
thumbnail = thumburl,
url = '/janusweb/index.html#janus.url=' .. EscapeFragment(url)
})
end
end
end
end)
local plugin = {
name = 'Federation',
tab = "<img src='img/link.svg' class='icon invert'/> Federation",
info = 'Add links to other (janus)XR(forges), and have them show up as extra portals in portalrooms',
opts = { -- TODO populate this based on xrforge-federation.json
{
key='app.opts.plugin_federation_title',
title='Title',
info="Displayed above federated portals",
default="Federated neighbourhoods",
values=''
},
{
key='app.opts.plugin_federation_strategy',
title='Display portals',
info="When to show federated portals?",
default="always",
default="homepage",
values={always='always',homepage='only homepage'}
},
{
key='app.opts.plugin_federation_url_1',
title='Federated URL 1',
info="URL + Thumbnail URL (space separated)",
info="URL and/or Thumbnail URL (space separated)",
default="https://xrforge.isvery.ninja https://i.imgur.com/x7fzn9v.png",
values=''
},
{
key='app.opts.plugin_federation_url_2',
title='Federated URL 2',
info="URL + Thumbnail URL (space separated)",
default="https://www.janusxr.org/new/ https://archive.org/download/janusxr-1/janusxr.png",
info="URL and/or Thumbnail URL (space separated)",
default="https://www.janusxr.org https://www.janusvr.com/images/logos/logotextoverlight.png",
values=''
},
{
key='app.opts.plugin_federation_url_3',
title='Federated URL 3',
info="URL + Thumbnail URL (space separated)",
info="URL and/or Thumbnail URL (space separated)",
default="",
values=''
},
{
key='app.opts.plugin_federation_url_4',
title='Federated URL 4',
info="URL + Thumbnail URL (space separated)",
info="URL and/or Thumbnail URL (space separated)",
default="",
values=''
},
{
key='app.opts.plugin_federation_url_5',
title='Federated URL 5',
info="URL + Thumbnail URL (space separated)",
info="URL and/or Thumbnail URL (space separated)",
default="",
values=''
}

View file

@ -26,7 +26,8 @@ end)
-- this registers the plugin UI
local plugin = {
name = 'Git',
info = '',
info = 'Git repositories will be cloned before xrforge starts scanning for experiences',
tab = "<img src='img/link.svg' class='icon invert'/> Git",
opts = {
{
key='app.opts.plugin_git_repo_1',

View file

@ -10,6 +10,38 @@ end)
app.on('set', function(k,v)
end)
app.on('init', function()
if app.opts.plugin_global_credible_exit == 'platform' then
if not util.fileExist( app.bin ) then -- check for binary in local filesystem
os.execute("ln -fs " .. arg[-1] .. " .")
os.execute("ln -fs " .. arg[-1] .. " " .. app.bin:gsub(".com",".zip"))
end
else
if not util.fileExist( app.bin ) then
os.execute("test -L " .. app.bin .. " && rm " .. app.bin ) -- delete symlink
end
end
end)
app.on('util.template', function(data)
if data.urlpath then
local isAdminPath = data.urlpath:match("^/admin")
if app.opts.plugin_global_credible_exit == 'platform' or
(app.opts.plugin_global_credible_exit == 'platformadmin' and isAdminPath)
then
data.footerlinks = data.footerlinks or {}
table.insert(data.footerlinks, {
title="<img class='icon download' src='/img/download.svg'>&nbsp;Download data",
url="/" .. app.bin:gsub(".com",".zip")
})
table.insert(data.footerlinks, {
title="<img class='icon download' src='/img/download.svg'>&nbsp;Download data+platform",
url="/" .. app.bin
})
end
end
end)
-- this registers the plugin UI
local plugin = {
name = "Global settings",
@ -27,13 +59,13 @@ local plugin = {
key='app.opts.plugin_global_subtitle',
title='Subtitle',
info='Slogan here',
default = "Portable XR experiences for Phone, VRheadset & desktop.",
default = "Interconnected XR experiences for Phone, VRheadset & desktop.",
values=''
},
{
key='app.opts.plugin_global_topmenu_mapname',
title='Overview link',
info='Top-right link <img class="icon list-tree" src="/img/list-tree.svg"> (overview page)',
info='Top-right link <img class="icon list-tree" src="img/list-tree.svg"> (overview page)',
default = "map",
values=''
},
@ -41,14 +73,21 @@ local plugin = {
key='app.opts.plugin_global_homepage',
title='Homepage',
info='Display 3D or 2D experiences on homepage',
default = "2D",
default = "3D",
values={ ['3D'] = '3D', ['2D'] = '2D' },
},
{
key='app.opts.plugin_global_credible_exit',
title='Credible exit',
info='How can users reproduce experiences locally?',
default = "platform",
values={ platform="download platform+data", platformadmin="download platform+data (admin only)" }
},
{
key='app.opts.plugin_global_topmenu_html',
title='Menu html',
info='Add extra menu items here',
default = '<a href="/about">about</a>',
default = '<a href="about">about</a>',
control='textarea',
values=''
},
@ -64,6 +103,13 @@ local plugin = {
control='textarea',
values=''
},
{
key='app.opts.plugin_global_remote_title',
title='Title',
info="Displayed above remote portals",
default="Remote / Federated",
values=''
},
},
middleware = {
homepage_2D_or_3D = function(req,res,next)

View file

@ -2,6 +2,7 @@ local util = require('util')
local pattern_fireboxroom = "[Ff][Ii][Rr][Ee][Bb][Oo][Xx][Rr][Oo][Oo][Mm][ ]?.*>"
local pattern_title = "<[Tt][Ii][Tt][Ll][Ee]>(.*)<"
local pattern_aframe = "<a%-scene"
app.on('scan_experience', function(exp)
--print_r(exp)
@ -32,8 +33,8 @@ app.on('scan_experience_file', function(exp)
if size < 100000 then -- skip large binary files > 100kb
local data = util.readfile( exp.file.fullpath)
if data then
if data:match(pattern_fireboxroom) then
table.insert(exp.tag, "janusxr")
if data:match(pattern_fireboxroom) or
data:match(pattern_aframe) then
local title = data:match(pattern_title)
if title then
exp.title = title:gsub("<.*","")
@ -41,8 +42,13 @@ app.on('scan_experience_file', function(exp)
app.pub("scan_experience_janusxr", nil, {exp=exp, item=item}) -- be nice to other plugins
app.cache.items[ exp.file.ino ] = exp
exp.view3D = true
exp.parent.redirect = exp.url -- mark parent to redirect to this standalone url instead (not glb assets e.g.)
exp.janusxr = {}
print("✅ └─ plugin/janusxr: " .. exp.file.name)
if data:match(pattern_aframe) and app.opts.plugin_janusxr_wrap_aframe == 'enabled' then
exp.janusxr.aframe = true
print("✅ └─ plugin/janusxr: AFRAME markup detected")
end
end
else
-- symlink to directory
@ -79,31 +85,31 @@ local plugin = {
},
{
key='app.opts.plugin_janusxr_wrap_3dfile',
title='wrap 3D file',
title='VR wrap 3D file',
info='render 3D files in <a href="https://coderofsalvation.github.io/janus-guide/#/build/room" target="_blank">janusxr.xml</a> room',
default="room_plane",
values=util.merge(rooms,{disabled="-- disabled --"})
},
{
key='app.opts.plugin_janusxr_wrap_image',
title='wrap images',
title='VR wrap images',
info='render images in <a href="https://coderofsalvation.github.io/janus-guide/#/build/room" target="_blank">janusxr.xml</a> room',
default="room_plane",
values=util.merge(rooms,{disabled="-- disabled --"})
},
{
key='app.opts.plugin_janusxr_wrap_video',
title='wrap videos',
title='VR wrap videos',
info='render videos in <a href="https://coderofsalvation.github.io/janus-guide/#/build/room" target="_blank">janusxr.xml</a> room',
default="room_plane",
values=util.merge(rooms,{disabled="-- disabled --"})
},
{
key='app.opts.plugin_janusxr_wrap_aframe',
title='wrap AFRAME',
info='render AFRAME markup in <a href="https://coderofsalvation.github.io/janus-guide/#/build/room" target="_blank">janusxr.xml</a> room',
default="room_plane",
values=util.merge(rooms,{disabled="-- disabled --"})
title='AFRAME support',
info='render <a href="https://aframe.io">AFRAME</a> (geometry) markup when detected in URLs',
default="enabled",
values={enabled="enabled",disabled="disabled"}
},
{
key='app.opts.plugin_janusxr_portalroom',

View file

@ -13,6 +13,7 @@ end)
-- this registers the plugin UI
local plugin = {
name = "Manyfold",
tab = "<img src='img/link.svg' class='icon invert'/> Manyfold",
info = '',
opts = {
{

85
src/plugin/peertube.lua Normal file
View file

@ -0,0 +1,85 @@
local util = require('util')
app.on('scan_experience', function(exp)
--print_r(exp)
end)
app.on('scan_experience_file', function(exp)
end)
app.on('set', function(k,v)
end)
app.on('dir', function(opts)
local showportals = app.opts.plugin_peertube_strategy == 'always' or
(app.opts.plugin_peertube_strategy == 'homepage' and opts.isroot)
if showportals then
opts.data.itemsremote = opts.data.itemsremote or {}
local itemsremote = opts.data.itemsremote
for i=1,5 do
local urls = app.opts[ 'plugin_peertube_url_' .. i]
if #urls > 0 then
local url = urls:gsub(' .*','')
local thumburl = urls:match(" ") and urls:gsub('.* ','') or nil
table.insert(itemsremote, {
title = url:gsub(".*//",""),
thumbnail = thumburl,
url = '/janusweb/index.html#janus.url=' .. EscapeFragment(url)
})
end
end
end
end)
-- this registers the plugin UI
local plugin = {
name = "Peertube",
tab = "<img src='img/link.svg' class='icon invert'/> Peertube",
info = "<b class='tag'>disabled</b> <a href='https://joinpeertube.org' target='_blank'>Peertube</a> videos/channels/livestreams can be rendered as rooms in <a href='https://coderofsalvation.github.io/janus-guide/#/build/peertube' target='_blank'>JanusWeb</a>",
opts = {
{
key='app.opts.plugin_peertube_strategy',
title='Display portals',
info="When to show peertube portals?",
default="homepage",
values={always='always',homepage='only homepage'}
},
{
key='app.opts.plugin_peertube_url_1',
title='Peertube URL 1',
info="URL and/or Thumbnail URL (space separated)",
default="https://tilvids.com/c/arthurpizza https://oss.iqrator.org/wp-content/uploads/2026/01/00-16-510x510.png",
values='https://tilvids.com/c/arthurpizza https://oss.iqrator.org/wp-content/uploads/2026/01/00-16-510x510.png'
},
{
key='app.opts.plugin_peertube_url_2',
title='Peertube URL 2',
info="URL and/or Thumbnail URL (space separated)",
default="",
values=''
},
{
key='app.opts.plugin_peertube_url_3',
title='Peertube URL 3',
info="URL and/or Thumbnail URL (space separated)",
default="",
values=''
},
{
key='app.opts.plugin_peertube_url_4',
title='Peertube URL 4',
info="URL and/or Thumbnail URL (space separated)",
default="",
values=''
},
{
key='app.opts.plugin_peertube_url_5',
title='Peertube URL 5',
info="URL and/or Thumbnail URL (space separated)",
default="",
values=''
}
}
}
return plugin

85
src/plugin/rssatom.lua Normal file
View file

@ -0,0 +1,85 @@
local util = require('util')
app.on('scan_experience', function(exp)
--print_r(exp)
end)
app.on('scan_experience_file', function(exp)
end)
app.on('set', function(k,v)
end)
app.on('dir', function(opts)
local showportals = app.opts.plugin_rssatom_strategy == 'always' or
(app.opts.plugin_rssatom_strategy == 'homepage' and opts.isroot)
if showportals then
opts.data.itemsremote = opts.data.itemsremote or {}
local itemsremote = opts.data.itemsremote
for i=1,5 do
local urls = app.opts[ 'plugin_rssatom_url_' .. i]
if #urls > 0 then
local url = urls:gsub(' .*','')
local thumburl = urls:match(" ") and urls:gsub('.* ','') or nil
table.insert(itemsremote, {
title = url:gsub(".*//",""),
thumbnail = thumburl,
url = '/janusweb/index.html#janus.url=' .. EscapeFragment(url)
})
end
end
end
end)
-- this registers the plugin UI
local plugin = {
name = "RSS Atom",
tab = "<img src='img/link.svg' class='icon invert'/> RSS Atom",
info = "<b class='tag'>disabled</b> RSS feeds can be rendered as rooms in <a href='https://coderofsalvation.github.io/janus-guide/#/build/feeds' target='_blank'>JanusWeb</a>",
opts = {
{
key='app.opts.plugin_rssatom_strategy',
title='Display portals',
info="When to show rss portals?",
default="homepage",
values={always='always',homepage='only homepage'}
},
{
key='app.opts.plugin_rssatom_url_1',
title='RSS/Atom URL 1',
info="URL and/or Thumbnail URL (space separated)",
default="http://mastodon.online/@lvk.rss https://pbs.twimg.com/profile_images/1604566357206601730/dlRNo-V-_400x400.jpg",
values='http://mastodon.online/@lvk.rss https://pbs.twimg.com/profile_images/1604566357206601730/dlRNo-V-_400x400.jpg'
},
{
key='app.opts.plugin_rssatom_url_2',
title='RSS/Atom URL 2',
info="URL and/or Thumbnail URL (space separated)",
default="https://nlnet.nl/feed.atom https://nlnet.nl/logo/history/logo-640x732-pre-2024.jpg",
values='https://nlnet.nl/feed.atom https://nlnet.nl/logo/history/logo-640x732-pre-2024.jpg'
},
{
key='app.opts.plugin_rssatom_url_3',
title='RSS/Atom URL 3',
info="URL and/or Thumbnail URL (space separated)",
default="",
values=''
},
{
key='app.opts.plugin_rssatom_url_4',
title='RSS/Atom URL 4',
info="URL and/or Thumbnail URL (space separated)",
default="",
values=''
},
{
key='app.opts.plugin_rssatom_url_5',
title='RSS/Atom URL 5',
info="URL and/or Thumbnail URL (space separated)",
default="",
values=''
}
}
}
return plugin

View file

@ -8,32 +8,10 @@ app.on('set', function(k,v)
end)
app.on('init', function()
local outdir = 'www'
local url = {"/"}
local me = app.plugin.staticwebgen
if util.contains(argv, 'generate') then
if util.fileExist(outdir) then
print("[!] folder 'www' was already detected..please delete it")
os.exit()
me.generate()
end
local selfbinary = '../' .. argv[-1]
os.execute("mkdir www")
os.execute("cp -Lr " .. app.opts.plugin_diskscan_dir .. " " .. outdir .. "/.")
os.execute("cd " .. outdir .. " && unzip " .. selfbinary .. " 'img/*' 'css/*' 'janusweb/*' 'shader/*'")
foreach( app.cache.items, function(k,v)
if v.dir then
table.insert(url, v.url )
end
end)
foreach( url, function(k,url)
url = url:gsub("//","/")
util.fetchServer( url, nil, function(req,res,next)
print( outdir .. url .. '/index.html')
util.writefile( outdir .. url .. '/index.html', res.body )
end)
end)
end
--print("files generated in 'www' folder")
--os.exit()
end)
-- this registers the plugin UI
@ -77,7 +55,47 @@ local plugin = {
default="",
values=''
}
}
},
done = function()
print("✅ finished generating 'www' folder")
os.exit(0)
end,
generate = function()
local outdir = 'www'
local root = app.opts.plugin_diskscan_dirname
local urls = {"/","/about", "/" .. root }
local urlLast = ''
if util.fileExist(outdir) then
print("[!] folder 'www' was already detected..please delete it")
os.exit()
end
local selfbinary = '../' .. argv[-1]
os.execute("mkdir www www/about")
os.execute("cp -Lr " .. app.opts.plugin_diskscan_dir .. " " .. outdir .. "/.")
os.execute("cd " .. outdir .. " && unzip " .. selfbinary .. " 'img/*' 'css/*' 'janusweb/*' 'shader/*'")
foreach( app.cache.items, function(k,v)
if v.dir then
table.insert(urls, v.url )
urlLast = v.url
end
end)
app.on('response', function(opts)
local body = opts.res._body -- capture the output
local url = opts.req.url:gsub("//","/")
util.writefile( outdir .. url .. '/index.html', body )
end)
foreach( urls, function(k,url)
url = url:gsub("//","/")
print("saving " .. url)
util.queueServerRequest('GET', url, nil, nil, nil, nil, nil, 8080)
app.run()
end)
app.plugin.staticwebgen.done()
end
}
return plugin

View file

@ -13,6 +13,7 @@ end)
-- this registers the plugin UI
local plugin = {
name = 'Webhooks <b class="tag">disabled</b>',
tab = "<img src='img/link.svg' class='icon invert'/> Webhooks",
info = '',
opts = {
{

View file

@ -1,17 +1,9 @@
local lust = require 'test/lust'
local util = require 'util'
local describe, it, expect = lust.describe, lust.it, lust.expect
-- usage: https://github.com/bjornbytes/lust
lust.queueServerRequest = function(method, path, params, host, headers, body, scheme, port)
GetMethod = function() return method end
GetPath = function() return path end
GetParams = function() return params or {} end -- note: { {"key","value"} } format
GetHost = function() return host or 'localhost' end
GetHeaders = function() return headers or {} end
GetScheme = function() return scheme or 'https' end
GetBody = function() return body or '' end
GetPort = function() return port or 80 end
end
lust.queueServerRequest = util.queueServerRequest
require('test/plugin_global')(lust)
require('test/plugin_git')(lust)

View file

@ -47,6 +47,7 @@ build(){
download janusweb
cd src
zip -yr ../result/bin/xrforge.com * .lua .init.lua .args
test -n "$ARCH" && cp ../result/bin/xrforge.com ../result/bin/xrforge-$ARCH.com
}
rebuild(){