commit 6089312e316b89177c472e6b9e8868dcf0c26520 Author: yuki Date: Tue Aug 19 18:01:48 2025 -0400 Premier commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..594cc74 --- /dev/null +++ b/LICENCE @@ -0,0 +1,70 @@ +Licence Libre du Québec – Permissive (LiLiQ-P) + +Version 1.1 + +1. Préambule +Cette licence s'applique à tout logiciel distribué dont le titulaire du droit d'auteur précise qu'il est sujet aux termes de la Licence Libre du Québec – Permissive (LiLiQ-P) (ci-après appelée la « licence »). + +2. Définitions +Dans la présente licence, à moins que le contexte n'indique un sens différent, on entend par: + + « concédant » : le titulaire du droit d'auteur sur le logiciel, ou toute personne dûment autorisée par ce dernier à accorder la présente licence; + « contributeur » : le titulaire du droit d'auteur ou toute personne autorisée par ce dernier à soumettre au concédant une contribution. Un contributeur dont sa contribution est incorporée au logiciel est considéré comme un concédant en regard de sa contribution; + « contribution » : tout logiciel original, ou partie de logiciel original soumis et destiné à être incorporé dans le logiciel; + « distribution » : le fait de délivrer une copie du logiciel; + « licencié » : toute personne qui possède une copie du logiciel et qui exerce les droits concédés par la licence; + « logiciel » : une œuvre protégée par le droit d'auteur, telle qu'un programme d'ordinateur et sa documentation, pour laquelle le titulaire du droit d'auteur a précisé qu'elle est sujette aux termes de la présente licence; + « logiciel dérivé » : tout logiciel original réalisé par un licencié, autre que le logiciel ou un logiciel modifié, qui produit ou reproduit la totalité ou une partie importante du logiciel; + « logiciel modifié » : toute modification par un licencié de l'un des fichiers source du logiciel ou encore tout nouveau fichier source qui incorpore le logiciel ou une partie importante de ce dernier. + +3. Licence de droit d'auteur +Sous réserve des termes de la licence, le concédant accorde au licencié une licence non exclusive et libre de redevances lui permettant d’exercer les droits suivants sur le logiciel : + + 1 Produire ou reproduire la totalité ou une partie importante; + 2 Exécuter ou représenter la totalité ou une partie importante en public; + 3 Publier la totalité ou une partie importante; + 4 Sous-licencier sous une autre licence libre, approuvée ou certifiée par la Free Software Foundation ou l'Open Source Initiative. + +Cette licence est accordée sans limite territoriale et sans limite de temps. + +L'exercice complet de ces droits est sujet à la distribution par le concédant du code source du logiciel, lequel doit être sous une forme permettant d'y apporter des modifications. Le concédant peut aussi distribuer le logiciel accompagné d'une offre de distribuer le code source du logiciel, sans frais supplémentaires, autres que ceux raisonnables afin de permettre la livraison du code source. Cette offre doit être valide pendant une durée raisonnable. + +4. Distribution +Le licencié peut distribuer des copies du logiciel, d'un logiciel modifié ou dérivé, sous réserve de respecter les conditions suivantes : + + 1 Le logiciel doit être accompagné d'un exemplaire de cette licence; + 2 Si le logiciel a été modifié, le licencié doit en faire la mention, de préférence dans chacun des fichiers modifiés dont la nature permet une telle mention; + 3 Les étiquettes ou mentions faisant état des droits d'auteur, des marques de commerce, des garanties ou de la paternité concernant le logiciel ne doivent pas être modifiées ou supprimées, à moins que ces étiquettes ou mentions ne soient inapplicables à un logiciel modifié ou dérivé donné. + +5. Contributions +Sous réserve d'une entente distincte, toute contribution soumise par un contributeur au concédant pour inclusion dans le logiciel sera soumise aux termes de cette licence. + +6. Marques de commerce +La licence n'accorde aucune permission particulière qui permettrait d'utiliser les marques de commerce du concédant, autre que celle requise permettant d'identifier la provenance du logiciel. + +7. Garanties +Sauf mention contraire, le concédant distribue le logiciel sans aucune garantie, aux risques et périls de l'acquéreur de la copie du logiciel, et ce, sans assurer que le logiciel puisse répondre à un besoin particulier ou puisse donner un résultat quelconque. + +Sans lier le concédant d'une quelconque manière, rien n'empêche un licencié d'offrir ou d'exclure des garanties ou du support. + +8. Responsabilité +Le licencié est responsable de tout préjudice résultant de l'exercice des droits accordés par la licence. + +Le concédant ne saurait être tenu responsable de dommages subis par le licencié ou par des tiers, pour quelque cause que ce soit en lien avec la licence et les droits qui y sont accordés. + +9. Résiliation +La présente licence est automatiquement résiliée dès que les droits qui y sont accordés ne sont pas exercés conformément aux termes qui y sont stipulés. + +Toutefois, si le défaut est corrigé dans un délai de 30 jours de sa prise de connaissance par la personne en défaut, et qu'il s'agit du premier défaut, la licence est accordée de nouveau. + +Pour tout défaut subséquent, le consentement exprès du concédant est nécessaire afin que la licence soit accordée de nouveau. + +10. Version de la licence +Le Centre de services partagés du Québec, ses ayants cause ou toute personne qu'il désigne, peuvent diffuser des versions révisées ou modifiées de cette licence. Chaque version recevra un numéro unique. Si un logiciel est déjà soumis aux termes d'une version spécifique, c'est seulement cette version qui liera les parties à la licence. + +Le concédant peut aussi choisir de concéder la licence sous la version actuelle ou toute version ultérieure, auquel cas le licencié peut choisir sous quelle version la licence lui est accordée. + +11. Divers +Dans la mesure où le concédant est un ministère, un organisme public ou une personne morale de droit public, créés en vertu d'une loi de l'Assemblée nationale du Québec, la licence est régie par le droit applicable au Québec et en cas de contestation, les tribunaux du Québec seront seuls compétents. + +La présente licence peut être distribuée sans conditions particulières. Toutefois, une version modifiée doit être distribuée sous un nom différent. Toute référence au Centre de services partagés du Québec, et, le cas échéant, ses ayant cause, doit être retirée, autre que celle permettant d'identifier la provenance de la licence. diff --git a/README.md b/README.md new file mode 100644 index 0000000..970e571 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Retroway BBS + +Un BBS Telidon tentant de recréer l'expérience du Videoway. \ No newline at end of file diff --git a/assets/apropos.svg b/assets/apropos.svg new file mode 100644 index 0000000..2f273aa --- /dev/null +++ b/assets/apropos.svg @@ -0,0 +1,16 @@ + + + + à propos + + + +Qui se souvient du fameux terminal Vidéoway de Vidéotron, avec ses menus, services interactifs, et ses nombreux jeux, actif entre 1990 et 2006? Ce service est une recréation non-officielle de cette interface, créée par un fan aussi près que possible de l'original, mais remise au goût du jour, comme s'il n'avait jamais disparu. + + + + + + OK <ENTRÉE> + + \ No newline at end of file diff --git a/assets/credits.svg b/assets/credits.svg new file mode 100644 index 0000000..fac0021 --- /dev/null +++ b/assets/credits.svg @@ -0,0 +1,17 @@ + + + + crédits + + + Programmation + a39 + Art + Yuki Hazuki + + + + + OK <ENTRÉE> + + \ No newline at end of file diff --git a/assets/info.png b/assets/info.png new file mode 100644 index 0000000..3c2d1a6 Binary files /dev/null and b/assets/info.png differ diff --git a/assets/infomenu.svg b/assets/infomenu.svg new file mode 100644 index 0000000..e0c03e1 --- /dev/null +++ b/assets/infomenu.svg @@ -0,0 +1,20 @@ + + + + informations + + + 1 à propos + 2 nouvelles + 3 aide + 4 crédits + + + 0 menu précédent + + + + + CHOIX <ENTRÉE> + + \ No newline at end of file diff --git a/assets/menu.svg b/assets/menu.svg new file mode 100644 index 0000000..e043ce5 --- /dev/null +++ b/assets/menu.svg @@ -0,0 +1,72 @@ + + + + + + + + + 1 + info + + + + + 2 + horloge + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bienvenue sur + videoway.online + + + + + + CHOIX <ENTRÉE> + + \ No newline at end of file diff --git a/assets/test.svg b/assets/test.svg new file mode 100644 index 0000000..da5b1e9 --- /dev/null +++ b/assets/test.svg @@ -0,0 +1,22 @@ + + +test page +small +normal +medium +doubleheight +double + !"#$%&'()*+,-./ +0123456789:;<=>? +@ABCDEFGHIJKLMNO +PQRSTUVWXYZ[\]^_ +`abcdefghijklmno +pqrstuvwxyz{|}~ + ¡¢£$¥#§¤‘“«←↑→↓ +°±²³×µ¶·÷’”»¼½¾¿ +―¹®©™♪─│╱╲◢◣⅛⅜⅝⅞ +ΩÆÐªĦ┼IJĿŁØŒºÞŦŊʼn +ĸæđðħıijŀłøœßþŧŋ +ÀÈÌÒÙàèìòùÉÍĹŃÓŔŚÚÝŹáćéģíĺńóŕśúýźÂĈÊĜĤÎĴÔŜÛŴŶâĉêĝĥîĵôŝûŵŷÃĨÑÕŨãĩñõũĀĒĪŌŪāēīōūĂĞŬăğŭĊĖĠİŻċėġżÄËÏÖÜŸäëïöüÿÅŮåůÇĢĶĻŅŖŞŢçķļņŗşţŰőűĄĘĮŲąęįųČĎĚĽŇŘŠŤŽčďěľňřšťž + + \ No newline at end of file diff --git a/assets/videoway.svg b/assets/videoway.svg new file mode 100644 index 0000000..07c79da --- /dev/null +++ b/assets/videoway.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/helpers/menu.js b/helpers/menu.js new file mode 100644 index 0000000..035772a --- /dev/null +++ b/helpers/menu.js @@ -0,0 +1,94 @@ +const { Color, Point } = require("telidon"); +const Page = require("./page.js"); + +module.exports = class Menu extends Page { + globalMenu = { + "#": "./views/mainmenu.js", + "#8378": "./views/test.js", + "*": ()=>this.gotoPrev(), + "*69": null, + "*911": ()=>{throw new Error("Écran de la mort qui tue causé par l'utilisateur")}, + } + + selection = ""; + menu = {}; + bgColor = new Color(0,0,0); + fgColor = new Color(1,1,1); + position = new Point(0.836,0); + maxLength = 5; + + constructor(naplps) { + super(naplps); + } + + start() { + //this.naplps.pointSetAbs(this.position); + //this.naplps.textMode(); + } + + data(byte) { + if(byte == 13) { + if(Object.keys(this.globalMenu).includes(this.selection)) + { + if(typeof this.globalMenu[this.selection] == "function") + this.globalMenu[this.selection](); + else + this.goto(this.globalMenu[this.selection]); + } + else if(Object.keys(this.menu).includes(this.selection)) + { + if(typeof this.menu[this.selection] == "function") + this.menu[this.selection](); + else + this.goto(this.menu[this.selection]); + } + else { + this.naplps.bell(); + if(this.selection.length > 0){ + this.naplps.pdiMode(); + this.naplps.pointSetAbs(this.position); + this.naplps.setColor(this.bgColor); + this.naplps.textMode(); + this.naplps.write(this.selection); + /*this.naplps.pdiMode(); + this.naplps.pointSetAbs(this.position); + this.naplps.setColor(this.fgColor); + this.naplps.textMode();*/ + this.selection = ""; + } + } + } + if((byte >= 48 && byte <= 57 && this.selection.length < this.maxLength) || + ((byte == 35 || byte == 42) && this.selection.length == 0)) + { + this.naplps.pdiMode(); + this.naplps.pointSetAbs(new Point(this.position.x + this.naplps.options.characterSize.x * this.selection.length, this.position.y)); + this.selection += String.fromCharCode(byte); + this.naplps.textMode(); + this.naplps.writeBytes(byte); + this.naplps.pdiMode(); + } + if(byte == 8) + { + if(this.selection.length > 0) + { + this.naplps.pdiMode(); + this.naplps.pointSetAbs(this.position); + this.naplps.setColor(this.bgColor); + this.naplps.text(this.selection); + this.naplps.pointSetAbs(this.position); + this.naplps.setColor(this.fgColor); + this.selection = this.selection.slice(0,-1); + this.naplps.text(this.selection); + } + else + { + this.naplps.bell(); + } + } + // TODO: if arrow keys, esc + } + + leave() { + } +} \ No newline at end of file diff --git a/helpers/page.js b/helpers/page.js new file mode 100644 index 0000000..32ace8e --- /dev/null +++ b/helpers/page.js @@ -0,0 +1,12 @@ +module.exports = class Page { + /** @type Naplps */ + naplps; + goto; + gotoPrev; + constructor(/** @type Naplps */naplps) { + this.naplps = naplps; + } + + data(d){} + leave(){} +} \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..c48eadd --- /dev/null +++ b/index.js @@ -0,0 +1,72 @@ +const { Telidon } = require("telidon"); +const fs = require("fs"); + +const curdir = process.cwd(); + +function invalidate(file) { + if(require.cache[require.resolve(file)]) { + require.cache[require.resolve(file)].children.forEach(c=>{ + if(c.filename.startsWith(curdir)) + invalidate(c.filename); + }); + delete require.cache[require.resolve(file)]; + } +} + +function watch(file) { + return fs.watch(file, { recursive: true }, (eventType, filename) => { + if(eventType == 'change') invalidate(file+filename); + }); +} + +watch("./helpers/"); +watch("./views/"); + +var server = new Telidon({}, c=>{ + var current = null; + var currentPage = "./views/mainmenu.js"; + var prevPage = null; + + c.debug = true; + c.options.multiLength = 5; + c.init(); + + c.on("error", (err) => { + console.error(`Caught exception: ${err.stack}\n`) + c.cancel(); + if(current != null) + current.leave(); + current = null; + c.uninit(); + c.write("\r\n\x1b[44m ÉCRAN DE LA MORT QUI TUE \x1b[0m\r\n\r\n"); + c.write("Le serveur a rencontré une erreur fatale :(\r\n\r\n"); + c.write("Vous pouvez nous aider en rapportant l'erreur suivante à nos admins:\r\n\r\n"); + c.write((err.stack||"").split("\n").flatMap(a=>a.match(/.{1,80}/g)||[]).join("\r\n")); + c.write("\r\n\r\nVous pouvez maintenant vous déconnecter et vous reconnecter de nouveau.\r\nBonne journée!\r\n"); + }); + + var goto = (page, save = true)=>{ + if(save) prevPage = currentPage; + if(!page) page = currentPage; + currentPage = page; + if(current != null) + { + c.cancel(); + current.leave(); + } + var pg = require(page); + current = new pg(c); + current.goto = goto; + current.gotoPrev = gotoPrev; + } + var gotoPrev = () => { + if(prevPage) goto(prevPage); + } + goto(); + c.on("data", d=>{ + if(current != null) + current.data(d) + }); +}); + +server.listen(8354); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..92edf4e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,42 @@ +{ + "name": "retrowaybbs", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "telidon": "file:../node-telidon" + } + }, + "../node-iconv": { + "name": "iconv", + "version": "3.0.1", + "extraneous": true, + "license": "ISC", + "engines": { + "node": ">=10.0.0" + } + }, + "../node-telidon": { + "name": "telidon", + "version": "0.1.0", + "hasInstallScript": true, + "license": "LiLiQ-P-1.1", + "dependencies": { + "color-rgba": "^3.0.0", + "iconv": "^3.0.1", + "jsdom": "^26.1.0", + "sharp": "^0.34.3" + }, + "devDependencies": { + "@types/color-rgba": "^2.1.3", + "@types/jsdom": "^21.1.7", + "@types/node": "^24.0.15" + } + }, + "node_modules/telidon": { + "resolved": "../node-telidon", + "link": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0e68ecc --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "main": "index.js", + "type": "commonjs", + "dependencies": { + "telidon": "file:../node-telidon" + } +} diff --git a/views/apropos.js b/views/apropos.js new file mode 100644 index 0000000..17e2053 --- /dev/null +++ b/views/apropos.js @@ -0,0 +1,17 @@ +const { Color } = require("telidon"); +const Menu = require("../helpers/menu.js"); + +module.exports = class AproposMenu extends Menu { + bgColor = new Color(0x80/255,0x20/255,0x80/255); + menu = { + "": "./views/info.js" + }; + constructor(naplps) { + super(naplps); + naplps.nsr(); + naplps.pdiMode(); + naplps.setDomain(); + + naplps.drawSvg("assets/apropos.svg").then(_=>this.start()); + } +} \ No newline at end of file diff --git a/views/credits.js b/views/credits.js new file mode 100644 index 0000000..8141124 --- /dev/null +++ b/views/credits.js @@ -0,0 +1,17 @@ +const { Color } = require("telidon"); +const Menu = require("../helpers/menu.js"); + +module.exports = class CreditsMenu extends Menu { + bgColor = new Color(0x80/255,0x20/255,0x80/255); + menu = { + "": "./views/info.js" + }; + constructor(naplps) { + super(naplps); + naplps.nsr(); + naplps.pdiMode(); + naplps.setDomain(); + + naplps.drawSvg("assets/credits.svg").then(_=>this.start()); + } +} \ No newline at end of file diff --git a/views/horloge.js b/views/horloge.js new file mode 100644 index 0000000..e432075 --- /dev/null +++ b/views/horloge.js @@ -0,0 +1,81 @@ +const { Color, Naplps, Point } = require("telidon"); +const Menu = require("../helpers/menu.js"); + +module.exports = class Horloge extends Menu { + bgColor = new Color(0.125,0.5,0.5); + fgColor = new Color(1,1,1); + menu = { + "": "./views/mainmenu.js" + }; + constructor(/** @type Naplps */naplps) { + super(naplps); + naplps.nsr(); + naplps.pdiMode(); + naplps.options.pelSize = new Point(0.01,0.01); + naplps.setDomain(); + naplps.setColor(new Color(0.125,0.5,0.5)); + naplps.setRectFilled([ + new Point(0,0), + new Point(1,0.75), + ]); + + naplps.setColor(new Color(1,1,1)); + naplps.setArcFilled([ + new Point(0.5,0.2), + new Point(0,0.5), + ]); + naplps.setColor(new Color(0,0,0)); + naplps.setArcOutlined([ + new Point(0.5,0.2), + new Point(0,0.5), + ]); + + var date = new Date(); + + this.interval = setInterval(()=>{ + naplps.pdiMode(); + naplps.doubleHeightText(); + naplps.setColor(new Color(0.125,0.5,0.5)); + naplps.pointSetAbs(new Point(0.4,0.1)); + naplps.text(date.toLocaleTimeString()); + naplps.setColor(new Color(1,1,1)); + naplps.pointSetAbs(new Point(0.5,0.45)); + var t = Math.floor((date.getTime()-date.getTimezoneOffset()*60000)/1000); + var h = t%43200/21600*Math.PI; + var m = t%3600/1800*Math.PI; + var s = t%60/30*Math.PI; + naplps.lineRel([ + new Point(Math.sin(h)*0.12,Math.cos(h)*0.12), + new Point(-Math.sin(h)*0.12,-Math.cos(h)*0.12), + new Point(Math.sin(m)*0.16,Math.cos(m)*0.16), + new Point(-Math.sin(m)*0.16,-Math.cos(m)*0.16), + new Point(Math.sin(s)*0.2,Math.cos(s)*0.2), + ]); + date = new Date(); + naplps.setColor(new Color(1,1,1)); + naplps.pointSetAbs(new Point(0.4,0.1)); + naplps.text(date.toLocaleTimeString()); + naplps.normalText(); + naplps.setColor(new Color(0,0,0)); + naplps.pointSetAbs(new Point(0.5,0.45)); + var t = Math.floor((date.getTime()-date.getTimezoneOffset()*60000)/1000); + var h = t%43200/21600*Math.PI; + var m = t%3600/1800*Math.PI; + var s = t%60/30*Math.PI; + naplps.lineRel([ + new Point(Math.sin(h)*0.12,Math.cos(h)*0.12), + new Point(-Math.sin(h)*0.12,-Math.cos(h)*0.12), + new Point(Math.sin(m)*0.16,Math.cos(m)*0.16), + new Point(-Math.sin(m)*0.16,-Math.cos(m)*0.16), + new Point(Math.sin(s)*0.2,Math.cos(s)*0.2), + ]); + }, 1000); + this.start(); + } + + leave() { + clearInterval(this.interval); + this.naplps.options.pelSize = new Point(0,0); + this.naplps.setDomain(); + } +} \ No newline at end of file diff --git a/views/info.js b/views/info.js new file mode 100644 index 0000000..53206c7 --- /dev/null +++ b/views/info.js @@ -0,0 +1,19 @@ +const { Color } = require("telidon"); +const Menu = require("../helpers/menu.js"); + +module.exports = class InfoMenu extends Menu { + bgColor = new Color(0x80/255,0x20/255,0x80/255); + menu = { + "0": "./views/mainmenu.js", + "1": "./views/apropos.js", + "4": "./views/credits.js" + }; + constructor(naplps) { + super(naplps); + naplps.nsr(); + naplps.pdiMode(); + naplps.setDomain(); + + naplps.drawSvg("assets/infomenu.svg").then(_=>this.start()); + } +} \ No newline at end of file diff --git a/views/mainmenu.js b/views/mainmenu.js new file mode 100644 index 0000000..bf1ac3c --- /dev/null +++ b/views/mainmenu.js @@ -0,0 +1,18 @@ +const { Color } = require("telidon"); +const Menu = require("../helpers/menu.js"); + +module.exports = class MainMenu extends Menu { + bgColor = new Color(0x20/255,0x60/255,0x80/255); + menu = { + "1": "./views/info.js", + "2": "./views/horloge.js" + }; + constructor(naplps) { + super(naplps); + naplps.nsr(); + naplps.pdiMode(); + naplps.setDomain(); + + naplps.drawSvg("assets/menu.svg").then(_=>this.start()); + } +} \ No newline at end of file diff --git a/views/test.js b/views/test.js new file mode 100644 index 0000000..d7de277 --- /dev/null +++ b/views/test.js @@ -0,0 +1,17 @@ +const { Color } = require("telidon"); +const Menu = require("../helpers/menu.js"); + +module.exports = class TestMenu extends Menu { + bgColor = new Color(0,0,0); + fgColor = new Color(1,1,1); + menu = { + }; + constructor(naplps) { + super(naplps); + naplps.nsr(); + naplps.pdiMode(); + naplps.setDomain(); + + naplps.drawSvg("assets/test.svg").then(_=>this.start()); + } +} \ No newline at end of file