Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
aa09303
overlay rooms via <link url="..." overlay="true"/>
coderofsalvation Apr 13, 2026
af76ca4
allow nested rooms via <link overlay='true'>
coderofsalvation Apr 15, 2026
46ed84f
extend support for <link auto_load=.. target=..> for nested rooms
coderofsalvation Apr 17, 2026
93f629c
code cleanup / reduce code
coderofsalvation Apr 17, 2026
4379e30
xrfragment: engine-prefix fix
coderofsalvation Apr 17, 2026
22c5d10
added 3D thumbnail example + positioning fix
coderofsalvation Apr 18, 2026
004a55c
nested rooms: let <paragraph> use css from own room-document
coderofsalvation Apr 18, 2026
2f29b1b
bugfix: timing issue texture loading
coderofsalvation Apr 19, 2026
f90c702
RSS translator + rsshub webui
coderofsalvation May 7, 2026
4313c13
disable cors-roundrobin (had suboptimal results)
coderofsalvation May 7, 2026
d2fd864
added rss-translator tests
coderofsalvation May 7, 2026
dcf4707
added rsshub webui
coderofsalvation May 7, 2026
1f663bc
better support for nonproxy + different rss-versions
coderofsalvation May 12, 2026
8b362d9
better messaging for rsshup webui app
coderofsalvation May 12, 2026
168a641
Merge branch 'master' into feat/overlayrooms
coderofsalvation May 13, 2026
b7fe91e
bugfix: dont bubble up portalclick to nested parents
coderofsalvation May 13, 2026
0aeb75d
added aframe translator + added aframe urls to tests/room/overlay_wor…
coderofsalvation May 14, 2026
c6b21dc
fix merge conflict
coderofsalvation May 14, 2026
0b73d7c
fix merge conflict
coderofsalvation May 14, 2026
8c2da16
bump version to 1.7.3
coderofsalvation May 14, 2026
7ad8a35
fix merge conflict
coderofsalvation May 14, 2026
4d359c3
dont assume .xml urls are rss/atom
coderofsalvation May 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@
- Build immersive 3D environments for desktop, mobile, and VR devices using HTML and JS
- Rendering functionality provided by Three.js / WebGL
- Separately host WebXR experiences via ([JML](https://madjin.github.io/janus-guide/#/examples/markup) in) any textfile.
- Basic support for RSS/ATOM feed-URLs (project existing content into 3D)
- Oculus Rift, Vive, GearVR, Daydream, and Cardboard support via WebVR API
- [Realtime collaboration](https://madjin.github.io/janus-guide/#/home/usecases) across all devices via built-in networking
- Import Collada, OBJ, glTF, and other popular 3d file formats
- Import Collada, OBJ, glTF, AFRAME geometry, and other popular 3d file formats
- Compose Hypergrids via URLs: render Janus worlds in worlds ([source](tests/room/overlay_worlds.xml) | [demo](https://web.janusxr.org/#janus.url=https://raw.githubusercontent.com/jbaicoianu/janusweb/refs/heads/master/tests/room/overlay_worlds.xml)), recursive worlds ([source](tests/room/overlay_recursive.xml) | [demo](https://web.janusxr.org/#janus.url=https://raw.githubusercontent.com/jbaicoianu/janusweb/refs/heads/master/tests/room/overlay_recursive.xml))
- 3D positional audio
- Gamepad support via the HTML5 Gamepad API
- Supports hand tracking peripherals like Leap Motion, Oculus Touch, and Vive controllers
Expand Down
58 changes: 58 additions & 0 deletions media/assets/translator/rss/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
app-rss {
display: block;
border: 1px solid black;
background: #900;
padding: .5em;
}

app-rss[done] {
background: #090;
}

:root{
--rss-bg: #171720;
--rss-fg: #eef;
}

.paragraphcontainer{
background: var(--rss-bg);
color: var(--rss-fg);
padding:15px 30px;
overflow:hidden;
font-size: 17px;
line-height:28px;
border-radius:15px;
width:100%;
height:100vh;
}

.paragraphcontainer .inline {
display:inline-block;
}

.paragraphcontainer img {
border-radius:10px;
max-height:50vh;
max-width:50vh;
margin: 0px 0px 15px 15px;
z-index:1;
float:right;
}

.paragraphcontainer #next{
background:#555;
color:black;
width:30px;
height:30px;
padding:10px;
font-size:23px;
text-align:center;
line-height:30px;
float:right;
border-radius:50%;
z-index:100;
}

.paragraphcontainer small {
color: #77F;
}
32 changes: 32 additions & 0 deletions media/assets/webui/apps/rsshub/routes.fetch
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env node
const fs = require("fs")

let output = {}


fetch('https://docs.rsshub.app/routes.json')
.then( (res) => res.json() )
.then( (json) => {
for( let domain in json ){
output[ domain ] = {routes:{}}
for( let route in json[domain].routes ){
// necessary tapdance to prevent codebase from being flagged
let safe = true
if( String(domain+route).match(/[p][o][r][n]/) ) safe = false
if( json[domain].routes[route].features && json[domain].routes[route].features.nsfw ) safe = false
if( safe ){
output[domain].routes[ route ] = {
example: json[domain].routes[route].example,
features: json[domain].routes[route].features || undefined
}
}
}
}
fs.writeFile('./routes.json', JSON.stringify(output), err => {
if (err) {
console.error(err);
} else {
console.log("[v] updated routes.json")
}
});
})
1 change: 1 addition & 0 deletions media/assets/webui/apps/rsshub/routes.json

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions media/assets/webui/apps/rsshub/rsshub.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
app-rss {
display: block;
border: 1px solid black;
background: #900;
padding: .5em;
}

app-rss[done] {
background: #090;
}
252 changes: 252 additions & 0 deletions media/assets/webui/apps/rsshub/rsshub.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
elation.elements.define('app-rsshub', class extends elation.elements.base {
// The init function gets called to define the object, and is used to define any attributes the element has
// Don't forget to call super.init() to set up any attributes which we've inherited!
init() {
super.init();
this.rss = elation.elements.app.rss
this.defineAttributes({
'done': { type: 'boolean', default: false }
});
/* TODO: in the future the autocomplete popup (navigator webui) could be refactored
* to show rsshub routes, but for now this is out of scope
*
* this.urlbar = elation.engine.instances.default.client.querySelector("janus-ui-urlbar")
* this.urlbar.suggestions.show()
* this.urlbar.suggestions.panel.innerHTML += `
* <div>
* <h2>RSS</h2>
* <ui-list collection="suggested_rss">
* <a href="{url}" class="suggestedroom" onclick="player.spawnPortal(this.href); player.enable(); return false;" title="{?title}{title} | {/title}{url}">
* <div>
* <h4>{?title}{title}{:else}{url}{/title}</h4>
* <h5>{url}</h5>
* </div>
* <img class="suggestedroom_thumb" src="{thumbnail}"/>
* </a>
* </ui-list>
* </div>
* `
*/
}
// The create function gets called for each instance of this element as it's added to the page
create() {
this.innerHTML = 'rsshub app loaded'
}
});

(function(){

const rsshub = elation.elements.app.rsshub

rsshub.url = {
routes: "https://docs.rsshub.app/routes.json"
}
rsshub.installed = false
rsshub.routes = {}
rsshub.instanceCurrent = -1
rsshub.instances = [
// https://github.com/RSSNext/rsshub-docs/blob/main/.vitepress/theme/components/InstanceList.vue#L37
{
url: 'https://rsshub.rssforever.com',
location: '🇦🇪',
maintainer: 'Stille',
maintainerUrl: 'https://www.ioiox.com',
},
{
url: 'https://hub.slarker.me',
location: '🇺🇸',
maintainer: 'Slarker',
maintainerUrl: 'https://slarker.me',
},
{
url: 'https://rsshub.pseudoyu.com',
location: '🇫🇷',
maintainer: 'pseudoyu',
maintainerUrl: 'https://github.com/pseudoyu',
},
{
url: 'https://rsshub.rss.tips',
location: '🇺🇸',
maintainer: 'AboutRSS',
maintainerUrl: 'https://github.com/AboutRSS/ALL-about-RSS',
},
{
url: 'https://rsshub.ktachibana.party',
location: '🇺🇸',
maintainer: 'KTachibanaM',
maintainerUrl: 'https://github.com/KTachibanaM',
},
{
url: 'https://rss.owo.nz',
location: '🇩🇪',
maintainer: 'Vincent Yang',
maintainerUrl: 'https://missuo.me',
},
{
url: 'https://rss.wudifeixue.com',
location: '🇨🇦',
maintainer: 'wudifeixue',
maintainerUrl: 'https://github.com/wudifeixue',
},
{
url: 'https://rss.littlebaby.life/rsshub',
location: '🇺🇸',
maintainer: 'yuanhong',
maintainerUrl: 'https://github.com/yuanhong078',
},
{
url: 'https://rsshub.henry.wang',
location: '🇬🇧',
maintainer: 'HenryQW',
maintainerUrl: 'https://github.com/HenryQW',
},
{
url: 'https://holoxx.f5.si/',
location: '🇯🇵',
maintainer: 'Vania',
maintainerUrl: 'https://note.com/vania',
},
{
url: 'https://rsshub.umzzz.com',
location: '🇭🇰',
maintainer: 'nesay',
maintainerUrl: 'https://umzzz.com',
},
{
url: 'https://rsshub.isrss.com',
location: '🇺🇸',
maintainer: 'isRSS',
maintainerUrl: 'https://isrss.com',
},
{
url: 'https://rsshub.email-once.com',
location: '🇭🇰',
maintainer: 'EmailOnce',
maintainerUrl: 'https://email-once.com',
},
{
url: 'https://rss.datuan.dev',
location: '🇻🇳',
maintainer: 'Tuấn Dev',
maintainerUrl: 'https://duonganhtuan.com',
},
{
url: 'https://rss.4040940.xyz',
location: '🇩🇪',
maintainer: 'TingyuShare',
maintainerUrl: 'https://github.com/TingyuShare',
},
{
url: 'https://rsshub.cups.moe',
location: '🇺🇸',
maintainer: 'FunnyCups',
maintainerUrl: 'https://www.cups.moe',
},
{
url: 'https://rss.spriple.org',
location: '🇨🇳',
maintainer: 'Spriple',
maintainerUrl: 'https://blog.spriple.org',
},
{
url: 'https://rsshub-balancer.virworks.moe',
location: '🇺🇳',
maintainer: 'chesha1',
maintainerUrl: 'https://github.com/chesha1',
}
]

rsshub.fetch = async function(){
if( rsshub.routes.length ) return
let routeURL = rsshub.url.routes
console.log("[rsshub] fetching routes from "+routeURL)
if (elation.engine.assets.corsproxy && routeURL.indexOf(elation.engine.assets.corsproxy) == -1) {
routeURL = elation.engine.assets.corsproxy + routeURL
}
let res = await fetch( routeURL )
let json = await res.json()
// the routes.json is huge..lets strip only necessary info
for( let domain in json ){
rsshub.routes[ domain ] = {routes:{}}
for( let route in json[domain].routes ){
if( json[domain].routes[route].example ){
rsshub.routes[domain].routes[ route ] = {
name: json[domain].routes[route].name,
example: json[domain].routes[route].example
}
}
}
}
}

rsshub.getRoutes = function(url){
const route = url.replace(/www\./,'')
.replace(/.*\/\//,'')
.replace(/\..*/,'')
if( rsshub.routes[ route ] ){
return rsshub.routes[ route ].routes
}
}

rsshub.install = async function(){
if( room?.translator != 'default' || rsshub.busy ) return
rsshub.busy = true
try{
await this.fetch()
await this.autoSelectInstance()

let routes = this.getRoutes(room.url)
if( routes ){ this.addPortals(routes) }
}catch(e){ console.error(e) }
rsshub.busy = false
}

rsshub.autoSelectInstance = async function(next){
const tryNext = async () => await rsshub.autoSelectInstance(true)
try{
if( rsshub.instanceCurrent == -1 || next){
rsshub.instanceCurrent += 1
console.log("[rsshub] checking instance "+rsshub.instances[ rsshub.instanceCurrent ].url)
if( rsshub.instanceCurrent < rsshub.instances.length ){
let instanceURL = rsshub.instances[ rsshub.instanceCurrent ].url
if (elation.engine.assets.corsproxy ){
instanceURL = elation.engine.assets.corsproxy + instanceURL
}
let res = await fetch( instanceURL )
let html = await res.text()
if( String(html).match('Welcome to RSSHub') ){
return console.log("[rsshub] found active instance")
}else await tryNext()
}
}
}catch(e){ tryNext() }
}

rsshub.addPortals = function(routes){
if( rsshub.instanceCurrent == -1 ) return // no instance found..no portal
let i = 1
for( let route in routes ){
const url = rsshub.instances[ rsshub.instanceCurrent ].url + routes[route].example
room.createObject('link',{
url,
pos: `-3.3 0 ${ -4 + (1.5*i++)}`,
rotation: '0 90 0',
shader_id: 'defaultportal',
round: true,
title: routes[route].name || route
})
if( i == 7 ) break;
}
room.createObject('text',{
pos: '-2.18 0 0.49',
scale: '4 4 1',
text: 'rsshub.app example feeds',
rotation: '-90 0 90'
})
}

// we don't know when we are included so we're betting on multiple horses
setTimeout( () => rsshub.install(), 5000 )
elation.events.add(null, 'room_load_complete', () => rsshub.install() );

})()
9 changes: 9 additions & 0 deletions media/assets/webui/apps/rsshub/rsshub.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"templates": {
},
"scripts": [ "./rsshub.js" ],
"css": ["./rsshub.css"],
"components":["app-rsshub"]
}


Loading