Compare commits
No commits in common. "master" and "ansitex" have entirely different histories.
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.idea
|
||||
.editorconfig
|
||||
*.debug
|
||||
dev/
|
@ -1,21 +0,0 @@
|
||||
image: docker:latest
|
||||
|
||||
stages:
|
||||
- build
|
||||
|
||||
variables:
|
||||
VERSION: 3.17c-${ARCH}
|
||||
CACHETAG: build-${ARCH}
|
||||
DOCKER_HOST: tcp://docker:2375
|
||||
|
||||
services:
|
||||
- docker:dind
|
||||
|
||||
before_script:
|
||||
- docker info
|
||||
- docker version
|
||||
- echo "$CI_JOB_TOKEN" | docker login -u "$CI_REGISTRY_USER" "$CI_REGISTRY" --password-stdin
|
||||
- ls -alR .
|
||||
|
||||
include: .gitlab-docker-x86_64.yml
|
||||
#include: .gitlab-docker-armv7l.yml
|
@ -1,16 +0,0 @@
|
||||
armv7l:build:
|
||||
variables:
|
||||
ARCH: armv7l
|
||||
stage: build
|
||||
image: docker:latest
|
||||
script:
|
||||
- if [ -f init ]; then chmod 500 init; fi
|
||||
- ([ -z "$REFRESH" ] && docker pull ${CI_REGISTRY_IMAGE}:${CACHETAG}) || echo "true"
|
||||
- docker build --cache-from ${CI_REGISTRY_IMAGE}:${CACHETAG} -t ${CI_REGISTRY_IMAGE}:${VERSION} -t ${CI_REGISTRY_IMAGE}:${CACHETAG} .
|
||||
- docker push ${CI_REGISTRY_IMAGE}:${VERSION}
|
||||
- docker push ${CI_REGISTRY_IMAGE}:${CACHETAG}
|
||||
tags:
|
||||
- docker
|
||||
- armv7l
|
||||
only:
|
||||
- master
|
@ -1,17 +0,0 @@
|
||||
x86_64:build:
|
||||
variables:
|
||||
ARCH: x86_64
|
||||
stage: build
|
||||
image: docker:latest
|
||||
script:
|
||||
- if [ -f init ]; then chmod 500 init; fi
|
||||
- ([ -z "$REFRESH" ] && docker pull ${CI_REGISTRY_IMAGE}:${CACHETAG}) || echo "true"
|
||||
- docker build --cache-from ${CI_REGISTRY_IMAGE}:${CACHETAG} -t ${CI_REGISTRY_IMAGE}:${VERSION} -t ${CI_REGISTRY_IMAGE}:${CACHETAG} .
|
||||
- docker push ${CI_REGISTRY_IMAGE}:${VERSION}
|
||||
- docker push ${CI_REGISTRY_IMAGE}:${CACHETAG}
|
||||
tags:
|
||||
- docker
|
||||
- x86_64
|
||||
only:
|
||||
- master
|
||||
|
66
Dockerfile
66
Dockerfile
@ -1,66 +0,0 @@
|
||||
# NAME leenooks/sbbs
|
||||
# VERSION 3.17c
|
||||
|
||||
FROM debian:stretch-slim
|
||||
|
||||
# Base utilities
|
||||
RUN apt-get update \
|
||||
&& apt-get install -yqq curl procps less zip unzip arj unrar-free lhasa arc zoo logrotate libmozjs185-1.0 cron \
|
||||
&& apt-get -y autoremove \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Add in Leenooks' apt repository
|
||||
RUN curl -s http://apt.leenooks.net/setup.sh | sh
|
||||
|
||||
# Add ZeroTier
|
||||
RUN echo "deb http://download.zerotier.com/debian/stretch stretch main" > /etc/apt/sources.list.d/zerotier.list
|
||||
|
||||
# Leenooks Utils
|
||||
RUN apt-get update \
|
||||
&& apt-get install -yqq --allow-unauthenticated makenl zerotier-one \
|
||||
&& apt-get -y autoremove \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Other Utilities
|
||||
RUN echo "deb http://ftp.au.debian.org/debian/ stretch contrib" > /etc/apt/sources.list.d/contrib.list
|
||||
RUN apt-get update \
|
||||
&& apt-get install -yqq dosemu \
|
||||
&& apt-get -y autoremove \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Build SBBS
|
||||
RUN apt-get update \
|
||||
&& apt-get install -yqq build-essential libnspr4-dev libncurses5-dev libmozjs185-dev cvs wget pkgconf \
|
||||
&& mkdir /opt/sbbs && cd /tmp \
|
||||
&& wget http://cvs.synchro.net/cgi-bin/viewcvs.cgi/*checkout*/install/GNUmakefile \
|
||||
&& make RELEASE=1 USE_DOSEMU=1 NO_X=1 JSINCLUDE=/usr/include/js JSLIB=mozjs185 SBBSDIR=/opt/sbbs install \
|
||||
&& rm -rf /opt/sbbs/3rdp /opt/sbbs/src \
|
||||
&& mv /opt/sbbs/ctrl /opt/sbbs/ctrl.orig \
|
||||
&& mkdir /opt/sbbs/nodes.orig && mv /opt/sbbs/node[1-4] /opt/sbbs/nodes.orig \
|
||||
&& ln -sf nodes/node1 /opt/sbbs/ \
|
||||
&& ln -sf nodes/node2 /opt/sbbs/ \
|
||||
&& ln -sf nodes/node3 /opt/sbbs/ \
|
||||
&& ln -sf nodes/node4 /opt/sbbs/ \
|
||||
&& find /opt/sbbs -name CVS -type d -exec rm -rf {} + \
|
||||
&& apt-get -y purge build-essential libnspr4-dev libncurses5-dev libmozjs185-dev cvs wget pkgconf \
|
||||
&& apt-get -y autoremove \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
WORKDIR /opt/sbbs
|
||||
ENV SBBSCTRL=/opt/sbbs/ctrl
|
||||
ENV SBBSEXEC=/opt/sbbs/exec
|
||||
ENV PATH=$PATH:${SBBSEXEC}
|
||||
|
||||
COPY init /sbin/init
|
||||
ENTRYPOINT [ "/sbin/init" ]
|
||||
CMD ["sbbs"]
|
||||
|
||||
VOLUME [ "/var/lib/zerotier-one" ]
|
||||
VOLUME [ "/opt/sbbs/data","/opt/sbbs/ctrl","/opt/sbbs/nodes","/opt/sbbs/fido" ]
|
||||
|
||||
# Set the default timezone for the container
|
||||
RUN ln -sf /usr/share/zoneinfo/Australia/Melbourne /etc/localtime
|
45
ctrl/videotex.ini
Normal file
45
ctrl/videotex.ini
Normal file
@ -0,0 +1,45 @@
|
||||
; Videotex Options
|
||||
|
||||
; Our key used to sign frames we send to other systems
|
||||
;gpg_key=0@videotex
|
||||
|
||||
[sqrl]
|
||||
auth_url=https://sqrl.dege.au
|
||||
auth_path=/sqrl/login
|
||||
auth_post=/api/sqrl
|
||||
|
||||
; The prefix configurations need to be kept in sync with other nodes
|
||||
; Frames with no specific owner are owned by this key
|
||||
; [prefix:x] - applies to pages begining with x
|
||||
; key= - the GPG key of the owner (for remote updates)
|
||||
; user= - the local user that can edit these frames (via *04)
|
||||
; logoans= - the service providers logo (for ANSI)
|
||||
; logovtx= - the service providers logo (for VIEWDATA)
|
||||
; code= - For Echomail, the echoarea tag
|
||||
; last_page= - For Echomail, the last page tagged
|
||||
|
||||
[prefix]
|
||||
key=0@videotex
|
||||
logoans=G1swbRtbMTszMW1BG1szMm1OG1szNG1TG1szM21JG1swOzMwOzQ3bXRleA==
|
||||
logovtx=AUECTgNTBEkHdGV4
|
||||
user=1
|
||||
|
||||
; System frames are owned by this key
|
||||
[prefix:9]
|
||||
key=0@videotex
|
||||
logoans=G1swbRtbMTszMW1BG1szMm1OG1szNG1TG1szM21JG1swOzMwOzQ3bXRleA==
|
||||
logovtx=AUECTgNTBEkHdGV4
|
||||
user=1
|
||||
|
||||
; ANSItex Help Pages
|
||||
[prefix:516]
|
||||
key=516@videotex
|
||||
logoans=G1swbRtbMTszMW1BG1szMm1OG1szNG1TG1szM21JG1swOzMwOzQ3bXRleA==
|
||||
logovtx=AUECTgNTBEkHdGV4
|
||||
user=1
|
||||
|
||||
; Private Net
|
||||
[prefix:10010]
|
||||
user=1
|
||||
logoans=UHJpdmF0ZU5ldA==
|
||||
logovtx=UHJpdmF0ZU5ldA==
|
24
init
24
init
@ -1,24 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
NAME="Synchronet BBS"
|
||||
|
||||
export PATH=$PATH:${SBBSEXEC}
|
||||
|
||||
if [ ! -e "${SBBSCTRL}/sbbs.ini" ]; then
|
||||
echo "* Installing SBBS ctrl files into ${SBBSCTRL}"
|
||||
cp -a ${SBBSCTRL}.orig/* ${SBBSCTRL}
|
||||
fi
|
||||
|
||||
if [ ! -d "${SBBSCTRL}/../nodes/node1" ]; then
|
||||
echo "* Installing SBBS nodes files into ${SBBSCTRL}/../nodes"
|
||||
cp -Ra ${SBBSCTRL}/../nodes.orig/* ${SBBSCTRL}/../nodes/
|
||||
fi
|
||||
|
||||
if [ -x /usr/sbin/zerotier-one -a -n "${ENABLE_ZT}" ]; then
|
||||
echo "* Starting ZeroTier"
|
||||
mkdir /dev/net && mknod /dev/net/tun -m 666 c 10 200
|
||||
/usr/sbin/zerotier-one -d
|
||||
fi
|
||||
|
||||
exec "$@"
|
17
install.txt
Normal file
17
install.txt
Normal file
@ -0,0 +1,17 @@
|
||||
26 May 2020
|
||||
Installation nodes.
|
||||
|
||||
1. For a new installation - make sure you have logged in first and created your sysop user.
|
||||
- No to creating guest account (or set it to a password so it isnt used - it asks questions during login about terminal configuration)
|
||||
2. Setup files
|
||||
- Replace answer.msg in text/ (or make a zero byte file)
|
||||
- in mods/ ln -sf ansitex/main.js login.js
|
||||
- in mods/ ln -sf ansitex/logon.js
|
||||
- in mods/ ln -sf ansitex/main.js ansitex.js
|
||||
- in mods/ baja ansitex/ansitex.src && mv ansitex/ansitex.bin . (NOT REQUIRED FROM 3.20a)
|
||||
3 - Create Shell: SCFG->Command Shells->Ansitex (optionally limit to FLAGS, but requires (requirement string) ANSI)
|
||||
- logon.js will force users to ansitex shell - to make a ini config setting for this
|
||||
- Optionally System -> New User Values -> Shells ->Ansitex (NOT REQUIRED with login.js in place)
|
||||
- Optionally Edit users and change their shell. (NOT REQUIRED with logon.js in place)
|
||||
4 - Turn off sysop password required for sysop login
|
||||
- SCFG -> Toggle Options -> Require Sys Pass During Login -> No
|
7
keys/genkey.txt
Normal file
7
keys/genkey.txt
Normal file
@ -0,0 +1,7 @@
|
||||
Key-Type: default
|
||||
Key-Usage: encrypt,sign
|
||||
Name-Real: Your Name
|
||||
Name-Comment: Ansitex Page *???#
|
||||
Name-Email: ???@videotex
|
||||
Expire-Date: 0
|
||||
%commit
|
82
load/control/echomail.js
Normal file
82
load/control/echomail.js
Normal file
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* This control renders echomail
|
||||
*
|
||||
* The system echomail prefix is *1, with echomail pages built from the following page number:
|
||||
* zzzzEEpppp, where:
|
||||
* + zzzz is the zone, zero padded, the zone identifies the message groups
|
||||
* + EE is the echomail area, ie: the message areas
|
||||
* + pppp is the message number, identified by the tag attached to the message header
|
||||
*
|
||||
* (Tags are added to the messages via an external process.)
|
||||
*/
|
||||
|
||||
// All controls need a unique variable so that require() can know if the control has already been loaded
|
||||
var CONTROL_ECHOMAIL = '1';
|
||||
|
||||
// Optional debug message so we can see that it is loaded
|
||||
log(LOG_DEBUG,'+ Control ECHOMAIL loaded');
|
||||
|
||||
// A unique method name (same as the control name that is called as new method() on initialisation
|
||||
function echomail(session,pagenum) {
|
||||
log(LOG_DEBUG,' - Loading echomail page:'+pagenum);
|
||||
|
||||
// has this control completed
|
||||
var complete = false;
|
||||
var ready = false;
|
||||
|
||||
function init(session,pagenum) {
|
||||
log(LOG_DEBUG,' - Echomail init('+pagenum+')');
|
||||
|
||||
ready = session.loadMessage(pagenum);
|
||||
}
|
||||
|
||||
Object.defineProperty(this, 'getName', {
|
||||
get: function () {
|
||||
return 'ECHOMAIL';
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'isComplete', {
|
||||
get: function () {
|
||||
return complete;
|
||||
}
|
||||
});
|
||||
|
||||
// Handle the keyboard responses as we receive them.
|
||||
this.handle = function(read) {
|
||||
log(LOG_DEBUG,'Control ECHOMAIL handle() start. ('+read+')');
|
||||
|
||||
switch(read) {
|
||||
case KEY_DOWN:
|
||||
session.page.scroll(0,1);
|
||||
read = '';
|
||||
break;
|
||||
|
||||
case KEY_UP:
|
||||
session.page.scroll(0,-1);
|
||||
read = '';
|
||||
break;
|
||||
}
|
||||
|
||||
session.render();
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
/**
|
||||
* ready() is called after a control has been initialised, to determine if the control will take the input
|
||||
* or if it is unable to do so
|
||||
*
|
||||
* If ready() returns:
|
||||
* + false, the main programming will return ERR_ROUTE to the user,
|
||||
* + true, the main programming will continue to load the frame, and then pass input to on the next loop handle()
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
this.ready = function() {
|
||||
log(LOG_DEBUG,'echomail:ready = '+JSON.stringify(ready));
|
||||
return ready;
|
||||
}
|
||||
|
||||
init.apply(this,arguments);
|
||||
}
|
157
load/control/frameedit.js
Normal file
157
load/control/frameedit.js
Normal file
@ -0,0 +1,157 @@
|
||||
load('ansiedit.js');
|
||||
|
||||
load('frame.js');
|
||||
load('tree.js');
|
||||
load('scrollbar.js');
|
||||
load('event-timer.js');
|
||||
load('graphic.js');
|
||||
|
||||
var CONTROL_FRAMEEDIT = '1';
|
||||
|
||||
function edit(session) {
|
||||
log(LOG_DEBUG,'+ Control EDIT loaded');
|
||||
var complete = false;
|
||||
var inProperty = false;
|
||||
|
||||
function init(session) {
|
||||
log(LOG_DEBUG,' - Edit init()');
|
||||
}
|
||||
|
||||
Object.defineProperty(this,'getName', {
|
||||
get: function() {
|
||||
return 'Frame Edit';
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this,'isComplete', {
|
||||
get: function() {
|
||||
return complete;
|
||||
}
|
||||
});
|
||||
|
||||
log(LOG_DEBUG,' - Owner: '+JSON.stringify(fo.owner));
|
||||
|
||||
const frame = new Frame(1,1,console.screen_columns,console.screen_rows,BG_BLACK|LIGHTGRAY);
|
||||
frame.gotoxy(1,1);
|
||||
|
||||
header = '\1n'+fo.pageownerlogo+' '.repeat(fo.settings.FRAME_HEADER-console.strlen(fo.pageownerlogo))+'\1n '+
|
||||
'\1W\1H'+fo.page+' '.repeat(fo.settings.FRAME_PAGENUM-fo.page.length)+' '+
|
||||
'\1G\1H'+' Edit';
|
||||
frame.putmsg(header);
|
||||
frame.open();
|
||||
|
||||
var editor = new ANSIEdit({
|
||||
x: 1,
|
||||
y: 2,
|
||||
width: 80,
|
||||
height: 23,
|
||||
attr: WHITE,
|
||||
//showPosition: true,
|
||||
menuHeading: 'Frame Edit '+fo.page,
|
||||
parentFrame: frame,
|
||||
});
|
||||
|
||||
editor.open();
|
||||
editor.menu.addItem('Properties',properties);
|
||||
editor.menu.addItem('Exit',onexit);
|
||||
editor.menu.addItem('Save & Exit',saveexit);
|
||||
|
||||
var x = new Graphic;
|
||||
x.ANSI = fo.parse(base64_decode(fo.content));
|
||||
log(LOG_DEBUG,' - Fields: '+JSON.stringify(fo.frame_fields));
|
||||
|
||||
const bin = x.BIN;
|
||||
var o = 0; // offset into 'bin'
|
||||
for (var yy = 0; yy < 22; yy++) {
|
||||
for (var xx = 0; xx < 80; xx++) {
|
||||
editor.putChar({
|
||||
x : xx,
|
||||
y : yy,
|
||||
ch : bin.substr(o, 1),
|
||||
attr : bin.substr(o + 1, 1).charCodeAt(0) || BG_BLACK
|
||||
});
|
||||
o = o + 2;
|
||||
}
|
||||
}
|
||||
|
||||
editor.cycle();
|
||||
frame.cycle();
|
||||
|
||||
this.handle=function(read) {
|
||||
if (! js.terminated) {
|
||||
switch (ascii(read)) {
|
||||
case 26:
|
||||
if (inProperty) {
|
||||
propFrame.close();
|
||||
frame.top();
|
||||
frame.cycle();
|
||||
inProperty = false;
|
||||
|
||||
} else {
|
||||
properties();
|
||||
}
|
||||
|
||||
return '';
|
||||
|
||||
case 27:
|
||||
if (inProperty) {
|
||||
log(LOG_DEBUG, ' + FrameEdit properties(): ESC');
|
||||
propFrame.close();
|
||||
frame.top();
|
||||
frame.cycle();
|
||||
inProperty = false;
|
||||
}
|
||||
|
||||
return '';
|
||||
|
||||
default:
|
||||
if (inProperty) {
|
||||
log(LOG_DEBUG, ' + FrameEdit properties(): read');
|
||||
propFrame.putmsg(read);
|
||||
propFrame.cycle();
|
||||
|
||||
} else {
|
||||
editor.getcmd(read);
|
||||
editor.cycle();
|
||||
frame.cycle();
|
||||
}
|
||||
|
||||
if (! complete)
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
editor.close();
|
||||
frame.close();
|
||||
fo.render();
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function properties() {
|
||||
inProperty = true;
|
||||
log(LOG_DEBUG, '+ FrameEdit properties()');
|
||||
frame.bottom();
|
||||
propFrame = new Frame(1,2,40,14,BG_BLUE|WHITE,frame);
|
||||
propFrame.gotoxy(1,1);
|
||||
propFrame.putmsg('Properties!');
|
||||
propFrame.open();
|
||||
propFrame.cycle();
|
||||
}
|
||||
|
||||
function save() {
|
||||
fo.content = base64_encode(editor.exportAnsi().join(''));
|
||||
fo.save();
|
||||
}
|
||||
|
||||
function onexit() {
|
||||
complete = true;
|
||||
}
|
||||
|
||||
function saveexit() {
|
||||
save();
|
||||
onexit();
|
||||
}
|
||||
|
||||
init.apply(this,arguments);
|
||||
}
|
215
load/control/register.js
Normal file
215
load/control/register.js
Normal file
@ -0,0 +1,215 @@
|
||||
/**
|
||||
* This handles user registration.
|
||||
*
|
||||
* The form must have the following fields:
|
||||
* + USER The user's user id to login
|
||||
* + EMAIL The users's email address - to receive tokens
|
||||
* + FULLNAME The user's full name
|
||||
* + PASS The users's preferred password
|
||||
* + CITY The user's city
|
||||
* + COUNTRY The user's country - 3 letter ISO code
|
||||
* + PCODE THe user's postal code
|
||||
*/
|
||||
|
||||
var CONTROL_REGISTER = '1';
|
||||
var EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
var cValChars='ACDEFHJKLMNPQRTUVWXY23456789!@$%&';
|
||||
|
||||
log(LOG_DEBUG,'+ Control REGISTER loaded');
|
||||
|
||||
function register(session) {
|
||||
var code = '';
|
||||
var complete = false;
|
||||
var processed = false;
|
||||
var ready = false;
|
||||
|
||||
function init(session) {
|
||||
log(LOG_DEBUG,' - Register init()');
|
||||
ready = true;
|
||||
}
|
||||
|
||||
// Called before processing for a field
|
||||
Object.defineProperty(this, 'getName', {
|
||||
get: function () {
|
||||
return 'Control-Registration';
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'isComplete', {
|
||||
get: function () {
|
||||
return complete && processed;
|
||||
}
|
||||
});
|
||||
|
||||
this.handle = function(read) {
|
||||
// Dont allow existing users to re-register
|
||||
if (user.number) {
|
||||
session.baselineSend('ALREADY_MEMBER',false);
|
||||
return (read === '*') ? read : '';
|
||||
}
|
||||
|
||||
log(LOG_DEBUG,'Control REGISTER handle() start. ('+read+')');
|
||||
if (cf === undefined) {
|
||||
log(LOG_DEBUG,' - CF not defined, returning');
|
||||
return read;
|
||||
}
|
||||
|
||||
log(LOG_DEBUG,'- Field '+cf.name+'('+JSON.stringify(cf)+')');
|
||||
|
||||
if ((cf.name === 'TOKEN') && (read === '#' || read === "\r")) {
|
||||
if (cf.value === code) {
|
||||
complete = true;
|
||||
|
||||
} else {
|
||||
session.baselineSend('INVALID_CODE',false);
|
||||
session.cursorOn(cf.c+cf.value.length,cf.r);
|
||||
session.attr(cf.attribute);
|
||||
read = '';
|
||||
}
|
||||
}
|
||||
|
||||
log(LOG_DEBUG,'- Field Value ['+cf.value+'] ('+code+')');
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
this.prefield = function() {
|
||||
log(LOG_DEBUG,'- prefield: Field '+cf.name+'('+JSON.stringify(cf)+')');
|
||||
|
||||
// Make sure we got an email
|
||||
if (cf.name === 'TOKEN') {
|
||||
if (! code.length) {
|
||||
log(LOG_DEBUG,' - BASELINE '+cf.name+'('+JSON.stringify(cf)+')');
|
||||
session.baselineSend('TOKEN_EMAIL',false);
|
||||
|
||||
var email = session.fieldValue('EMAIL');
|
||||
var uid = session.fieldValue('USER');
|
||||
var name = session.fieldValue('FULLNAME');
|
||||
|
||||
log(LOG_DEBUG,' - VALIDATE EMAIL TO ('+JSON.stringify(system.matchuserdata(U_NETMAIL,email))+')');
|
||||
|
||||
// Validate Email hasnt been used
|
||||
// Validate USER_ID hasnt been used
|
||||
if ((email.indexOf('@') === -1) || ! EMAIL_REGEX.test(email) || (system.matchuserdata(U_NETMAIL,email) !== 0)) {
|
||||
session.baselineSend('INVALID_EMAIL',false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (! system.check_name(uid)) {
|
||||
log(LOG_DEBUG,' - Cannot use user_id: ('+uid+')');
|
||||
session.baselineSend('INVALID_UID',false);
|
||||
return;
|
||||
}
|
||||
|
||||
var msgbase = new MsgBase('mail');
|
||||
for (var i=0;i<6;i++) {
|
||||
code += cValChars.substr(parseInt(Math.random()*cValChars.length),1);
|
||||
}
|
||||
|
||||
var hdrs = new Object();
|
||||
hdrs.to = name;
|
||||
hdrs.to_net_type = netaddr_type(email);
|
||||
|
||||
if (hdrs.to_net_type !== NET_NONE) {
|
||||
hdrs.to_net_addr = email;
|
||||
} else {
|
||||
session.baselineSend('CANNOT_SEND_TOKEN',false);
|
||||
return;
|
||||
}
|
||||
|
||||
hdrs.from = system.name;
|
||||
hdrs.from_net_addr = 'sysop@'+system.inet_addr;
|
||||
hdrs.from_net_type = NET_INTERNET;
|
||||
hdrs.subject = 'Registration TOKEN for '+system.name;
|
||||
|
||||
if (msgbase.open !== undefined && msgbase.open() === false) {
|
||||
console.print("\r\n\1n\1h\1rERROR: \1y" + msgbase.last_error + "\1n \r\n");
|
||||
console.pause();
|
||||
msgbase.close();
|
||||
bbs.hangup();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var msg="CODE: " + code + "\r\n\r\n";
|
||||
msg += 'Please use the above code to validate your login to '+system.name+'.';
|
||||
|
||||
//log(LOG_DEBUG,' - HDR:'+JSON.stringify(hdrs));
|
||||
//log(LOG_DEBUG,' - MSG:'+JSON.stringify(msg));
|
||||
|
||||
if (! msgbase.save_msg(hdrs,msg)) {
|
||||
console.print("\r\n\1n\1h\1rERROR: \1y" + msgbase.last_error + "\1n \r\n");
|
||||
console.pause();
|
||||
msgbase.close();
|
||||
bbs.hangup();
|
||||
return;
|
||||
}
|
||||
|
||||
msgbase.close();
|
||||
}
|
||||
|
||||
session.baselineSend('TOKEN_SENT',false);
|
||||
log(LOG_DEBUG,'SENT EMAIL TOKEN ('+code+') ['+JSON.stringify(hdrs)+']');
|
||||
}
|
||||
}
|
||||
|
||||
this.process = function() {
|
||||
log(LOG_DEBUG,'Creating user: ['+session.fieldValue('EMAIL')+'] ['+session.fieldValue('USER')+']');
|
||||
|
||||
try {
|
||||
var newuser = system.new_user(session.fieldValue('USER'));
|
||||
|
||||
} catch (e) {
|
||||
session.baselineSend('USER_EXISTS',false);
|
||||
log(LOG_ERROR,"New user couldn't be created (user created while signing up)");
|
||||
log(LOG_ERROR,JSON.stringify(e));
|
||||
processed = true;
|
||||
return this.isComplete;
|
||||
}
|
||||
|
||||
if (typeof newuser === 'number') {
|
||||
session.baselineSend('USER_CREATE_ERROR',false);
|
||||
log(LOG_ERROR,"New user couldn't be created (error code "+newuser+")");
|
||||
processed = true;
|
||||
return this.isComplete;
|
||||
}
|
||||
|
||||
newuser.security.password = '';
|
||||
if (bbs.login(newuser.alias,null)) {
|
||||
user.number = newuser.number;
|
||||
user.security.password = session.fieldValue('PASS');
|
||||
user.name = session.fieldValue('FULLNAME');
|
||||
user.handle = session.fieldValue('USER');
|
||||
user.location = session.fieldValue('CITY')+', '+session.fieldValue('COUNTRY');
|
||||
user.zipcode = session.fieldValue('PCODE');
|
||||
user.netmail = session.fieldValue('EMAIL');
|
||||
user.comment = 'ANSItex registered user';
|
||||
bbs.user_sync();
|
||||
bbs.logon();
|
||||
|
||||
log(LOG_INFO,"Created user record #"+user.number+": "+user.alias);
|
||||
|
||||
action = ACTION_EXIT;
|
||||
processed = true;
|
||||
return this.isComplete;
|
||||
|
||||
} else {
|
||||
session.baselineSend('LOGIN_ERROR',false);
|
||||
log(LOG_INFO,"bbs.login() failed");
|
||||
user.comment = 'Initial login failed!';
|
||||
newuser.settings |= USER_DELETED;
|
||||
delete newuser;
|
||||
processed = true;
|
||||
|
||||
return this.isComplete;
|
||||
}
|
||||
}
|
||||
|
||||
// @todo Doesnt appear to be used
|
||||
this.ready = function() {
|
||||
log(LOG_DEBUG,'register:ready = '+JSON.stringify(ready));
|
||||
return ready;
|
||||
}
|
||||
|
||||
init.apply(this,arguments);
|
||||
}
|
205
load/control/sqrllogin.js
Normal file
205
load/control/sqrllogin.js
Normal file
@ -0,0 +1,205 @@
|
||||
/**
|
||||
* This handles user registration.
|
||||
*
|
||||
* The form must have the following fields:
|
||||
* + UID The user's user id to login
|
||||
* + EMAIL The users's email address - to receive tokens
|
||||
* + FULLNAME The user's full name
|
||||
* + PASS The users's preferred password
|
||||
* + CITY The user's city
|
||||
* + COUNTRY The user's country - 3 letter ISO code
|
||||
* + PCODE THe user's postal code
|
||||
*/
|
||||
|
||||
var CONTROL_SQRL = '1';
|
||||
|
||||
require('http.js','HTTPRequest');
|
||||
load('ansitex/load/qrcode-make.js');
|
||||
load('frame.js');
|
||||
|
||||
log(LOG_DEBUG,'+ Control SQRL-LOGIN loaded');
|
||||
|
||||
// @todo This should move to handle - since we have to press 2 twice to get out.
|
||||
function sqrllogin() {
|
||||
var complete = false;
|
||||
var cancel = false;
|
||||
|
||||
var sqrl = loadOptions('sqrl');
|
||||
log(LOG_DEBUG,'OPTIONS: '+JSON.stringify(sqrl));
|
||||
var http = new HTTPRequest();
|
||||
http.SetupGet(sqrl.auth_path,undefined,sqrl.auth_url);
|
||||
http.request_headers.push('Accept: application/json');
|
||||
|
||||
try {
|
||||
http.SendRequest();
|
||||
http.ReadResponse();
|
||||
log(LOG_INFO,'SQRL: '+JSON.stringify(http.body));
|
||||
|
||||
var data = http.body
|
||||
.split('')
|
||||
.map(function(x) {return x.charCodeAt(0)});
|
||||
var qr = qrcodegen.QrCode.encodeBinary(data,qrcodegen.QrCode.Ecc.LOW);
|
||||
|
||||
var subframe = new Frame((viewdata ? fo.settings.FRAME_WIDTH : fo.settings.FRAME_WIDTH-qr.size-2),2,(viewdata ? qr.size/2 : qr.size+2),22,BG_BLACK|LIGHTGRAY);
|
||||
fo.qrcode(qr,subframe);
|
||||
|
||||
fo.baselineSend('CANCEL_MSG',false);
|
||||
|
||||
// Loop and see if the user has logged in
|
||||
var nut = http.body.substr(http.body.indexOf('nut='),68);
|
||||
|
||||
} catch (err) {
|
||||
log(LOG_INFO,'SQRL Error: '+err+' '+JSON.stringify(sqrl));
|
||||
cancel = true;
|
||||
}
|
||||
|
||||
// Called before processing for a field
|
||||
Object.defineProperty(this, 'getName', {
|
||||
get: function () {
|
||||
return 'SQRL-LOGIN';
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'isComplete', {
|
||||
get: function () {
|
||||
return complete;
|
||||
}
|
||||
});
|
||||
|
||||
// Nothing to do here
|
||||
this.handle=function(read) {
|
||||
log(LOG_DEBUG,'Control SQRL-LOGIN handle() start. ('+read+')');
|
||||
|
||||
if (read === '2') {
|
||||
log(LOG_INFO,'SQRL: Cancelled with 2');
|
||||
cancel = true;
|
||||
|
||||
} else {
|
||||
log(LOG_DEBUG,'SQRL read ['+read+']');
|
||||
|
||||
try {
|
||||
http = new HTTPRequest();
|
||||
http.SetupGet(sqrl.auth_post+'?'+nut,undefined,sqrl.auth_url);
|
||||
http.request_headers.push('Accept: application/json');
|
||||
|
||||
log(LOG_DEBUG,'Checking NUT in ['+nut+']');
|
||||
http.SendRequest();
|
||||
http.ReadResponse();
|
||||
|
||||
switch (http.response_code) {
|
||||
case 404:
|
||||
log(LOG_DEBUG,'- NUT not Authorised yet.');
|
||||
break;
|
||||
|
||||
case 200:
|
||||
var result = JSON.parse(http.body);
|
||||
|
||||
if (result.isReady) {
|
||||
log(LOG_INFO,'NUT: '+result.msg);
|
||||
log(LOG_INFO,'NEXT: '+result.nextPage);
|
||||
if (result.msg === 'SQRL authenticated') {
|
||||
log(LOG_DEBUG,'Getting Authenticated User ['+result.nextPage+']');
|
||||
http = new HTTPRequest();
|
||||
http.SetupGet(result.nextPage,undefined,'');
|
||||
http.request_headers.push('Accept: application/json');
|
||||
|
||||
http.SendRequest();
|
||||
http.ReadResponse();
|
||||
|
||||
log(LOG_DEBUG,'Getting Authenticated User Response ['+http.response_code+']');
|
||||
if (http.response_code === 200) {
|
||||
var sqrluser = http.body.substr(0,40);
|
||||
var username = 'S'+sqrluser.substr(0,24)
|
||||
log(LOG_DEBUG,'Getting Authenticated sqrluser ['+JSON.stringify(sqrluser)+']');
|
||||
|
||||
// Look through our user base for an existing user
|
||||
var uid = system.matchuser(username);
|
||||
|
||||
log(LOG_DEBUG,'Getting Authenticated UID ['+JSON.stringify(uid)+']');
|
||||
if (! uid) {
|
||||
log(LOG_DEBUG,'New User ['+username+'] with pass ('+sqrluser+')');
|
||||
var user = system.new_user(username);
|
||||
log(LOG_DEBUG,'New User ['+JSON.stringify(user.number)+']');
|
||||
user.name = username;
|
||||
user.security.password = sqrluser;
|
||||
user.handle = username.substr(0,8);
|
||||
user.location = 'Earth';
|
||||
user.zipcode = '000';
|
||||
user.netmail = username+'@'+system.inet_addr;
|
||||
|
||||
user.comment = 'ANSItex registered user - with SQRL';
|
||||
bbs.user_sync();
|
||||
|
||||
} else {
|
||||
user = new User(uid);
|
||||
}
|
||||
|
||||
log(LOG_DEBUG,'Getting Authenticated USER ['+JSON.stringify(user.number)+']');
|
||||
|
||||
// Existing user, we'll exit here
|
||||
if (bbs.login(user.name,null,user.security.password)) {
|
||||
log(LOG_DEBUG,' - User:'+JSON.stringify(user.number));
|
||||
bbs.logon();
|
||||
log(LOG_DEBUG,' - SEND TO EXIT:');
|
||||
|
||||
complete = true;
|
||||
action = ACTION_EXIT;
|
||||
if (typeof subframe === 'object')
|
||||
subframe.close();
|
||||
|
||||
break;
|
||||
|
||||
} else {
|
||||
log(LOG_ERROR,'- Login Failed? ');
|
||||
cancel = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
log(LOG_ERROR,'- Unhandled User Details: '+http.response_code);
|
||||
cancel = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
log(LOG_ERROR,'- Unhandled isReady msg: '+result.msg);
|
||||
cancel = true;
|
||||
}
|
||||
|
||||
complete = true;
|
||||
if (typeof subframe === 'object')
|
||||
subframe.close();
|
||||
|
||||
} else {
|
||||
log(LOG_ERROR,'- Unhandled isReady: '+result.isReady);
|
||||
cancel = true;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
log(LOG_ERROR,'- Unhandled response code: '+http.response_code);
|
||||
|
||||
// We are done
|
||||
cancel = true;
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
log(LOG_INFO,'SQRL Error: '+err+' '+JSON.stringify(sqrl));
|
||||
cancel = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (cancel) {
|
||||
log(LOG_INFO,'SQRL: Processing CANCEL ['+read+'].');
|
||||
complete = true;
|
||||
if (typeof subframe === 'object')
|
||||
subframe.close();
|
||||
|
||||
action = ACTION_GOTO;
|
||||
next_page = FRAME_LOGIN;
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
}
|
||||
|
||||
this;
|
161
load/defs.js
Normal file
161
load/defs.js
Normal file
@ -0,0 +1,161 @@
|
||||
require('smbdefs.js','MSG_DELETE');
|
||||
require('sbbsdefs.js','SUB_FIDO');
|
||||
load('string.js');
|
||||
load('lz-string.js');
|
||||
|
||||
/**
|
||||
* ANSItex definitions
|
||||
*/
|
||||
|
||||
/* Our home on disk */
|
||||
const ANSITEX_HOME = system.mods_dir+'ansitex';
|
||||
/* Frames location */
|
||||
const FRAMES_HOME = ANSITEX_HOME+'/text/';
|
||||
/* Load frames from msgbase */
|
||||
const FRAMES_MSG_BASE = 'vtx_data';
|
||||
/* Load frames from files */
|
||||
const FRAMES_MSG_FILES = true;
|
||||
const FRAMES_EOF_MARKER = "\r\n=== EOF";
|
||||
|
||||
const PAGE_LENGTH = 4; // The size of our page tag as stored in the msgbase for echomail/netmail
|
||||
const NUMERIC_REGEX = /^[0-9]*$/;
|
||||
|
||||
/* Unit of cost */
|
||||
const FRAME_COSTUNIT = 'c';
|
||||
|
||||
/** ACTIONS **/
|
||||
|
||||
/* Exit the script */
|
||||
const ACTION_EXIT = 99;
|
||||
/* Reload the current frame */
|
||||
const ACTION_RELOAD = 1;
|
||||
/* Goto a specific frame */
|
||||
const ACTION_GOTO = 2;
|
||||
/* Goto previous frame */
|
||||
const ACTION_BACKUP = 3;
|
||||
/* Goto next frame */
|
||||
const ACTION_NEXT = 4;
|
||||
/* Terminate the session */
|
||||
const ACTION_TERMINATE = 5;
|
||||
/* Submit form contents */
|
||||
const ACTION_SUBMITRF = 6;
|
||||
/* Star command entry */
|
||||
const ACTION_STAR = 7;
|
||||
/* Edit a frame */
|
||||
const ACTION_EDIT = 8;
|
||||
|
||||
/** MODES **/
|
||||
|
||||
/* Typing * command on baseline */
|
||||
const MODE_BL = 1;
|
||||
/* Field Input */
|
||||
const MODE_FIELD = 2;
|
||||
/* Asking if form should be submitted */
|
||||
const MODE_SUBMITRF = 3;
|
||||
/* Response frame not sent */
|
||||
const MODE_RFNOTSENT = 4;
|
||||
/* Response frame sent */
|
||||
const MODE_RFSENT = 5;
|
||||
/* Response frame error */
|
||||
const MODE_RFERROR = 6;
|
||||
|
||||
/** FRAME TYPES **/
|
||||
// @todo Change these to bits
|
||||
|
||||
/* Information Frame, requires no response after viewed */
|
||||
const FRAME_TYPE_INFO = 0; //'i';
|
||||
/* Terminate Frame, contents displayed and then carrier dropped */
|
||||
const FRAME_TYPE_TERMINATE = 15; //'t';
|
||||
/**
|
||||
* Frame the calls an External Method
|
||||
* Contents indicate the method to be called with arguments
|
||||
*/
|
||||
const FRAME_TYPE_EXTERNAL = 13; //'x';
|
||||
/**
|
||||
* Frame calls a door
|
||||
* Contents indicate the name of the door
|
||||
*/
|
||||
const FRAME_TYPE_DOOR = 11; //'X';
|
||||
/**
|
||||
* Frame renders a BBS from sbbslist
|
||||
* Contents can be:
|
||||
* + preview(bbs) - to show the BBS preview display
|
||||
* + telnet(bbs) - to telgate to the BBS
|
||||
*/
|
||||
const FRAME_TYPE_BBS = 'b';
|
||||
/**
|
||||
* Response frame, input fields are embedded in the frame and after input the
|
||||
* response will be submitted to the Service Provider, or to a method
|
||||
*/
|
||||
const FRAME_TYPE_RESPONSE = 1; //'r';
|
||||
/* Login frame, enables the user to authenticate to the system, or to a CUG */
|
||||
const FRAME_TYPE_LOGIN = 12; //'l';
|
||||
/* Mail template frames - mail templates will have the user's stats for the area passed to render() */
|
||||
const FRAME_TYPE_MAIL_TEMPLATE = 'm';
|
||||
/* Frame is a message */
|
||||
const FRAME_TYPE_MESSAGE = 2; // 'M';
|
||||
|
||||
/* Disable *# going backwards for the following frames */
|
||||
const FRAMES_NO_HISTORY = ['980a','98b','981a','982a','983a','998a'];
|
||||
|
||||
/* Frames prefixed with this are owned by the system */
|
||||
const SYSTEM_OWNER = 9;
|
||||
// @todo can we get this from the message base?
|
||||
const SYSTEM_ZONE = 516;
|
||||
|
||||
/* Time to wait for a key press */
|
||||
const INACTIVE_TIMEOUT = 100000;
|
||||
/* Idle time for un-authenticated users */
|
||||
const INACTIVE_NOLOGIN = 30000;
|
||||
/* Idle time for authenticated users */
|
||||
const INACTIVE_LOGIN = 5*60000;
|
||||
|
||||
/* Home Frame */
|
||||
const FRAME_HOME = {frame: '1',index: 'a'};
|
||||
/* Login Frame */
|
||||
const FRAME_LOGIN = {frame: '98',index: 'a'};
|
||||
/* Registration Frame */
|
||||
const FRAME_REGISTER = {frame: '981',index: 'a'};
|
||||
/* SQRL Login */
|
||||
const FRAME_SQRL = {frame: '982',index: 'a'};
|
||||
/* Login Failed */
|
||||
const FRAME_LOGIN_FAILED = {frame: '983',index: 'a'};
|
||||
/* Home page after authentication */
|
||||
const FRAME_HOME_AUTH = {frame: '98',index: 'b'};
|
||||
/* Home page for initial connection */
|
||||
const FRAME_HOME_CONNECT = {frame: '980',index: 'a'};
|
||||
const FRAME_SYSTEM_ERROR = {frame: '998',index: 'a'};
|
||||
|
||||
/* Attributes saved/loaded from files */
|
||||
const FRAME_SAVE_ATTRS = [
|
||||
'content', // raw source content of a frame (as stored in vtx/tex files)
|
||||
'cost', // integer, frame cost
|
||||
'date', // date, date frame created
|
||||
'dynamic_fields', // array of fields // @todo deprecate and work these out when loading and parsing a frame
|
||||
'frame', // Page ID,
|
||||
'index', // Page index,
|
||||
'input_fields', // array of fields // @todo deprecate and work these out when loading and parsing a frame
|
||||
'isAccessible', // boolean // @todo deprecate and merge into 'state'
|
||||
'isPublic', // boolean // @todo deprecate and merge into 'state'
|
||||
'key', // array, representing our key actions (0-9)
|
||||
'state', // integer, representing type, accessible and public status
|
||||
'type', // frame type // @todo deprecate and merge into 'state'
|
||||
'version', // frame version (1)
|
||||
'window' // processed frame data
|
||||
];
|
||||
|
||||
/* The page that has our echomail area reading template */
|
||||
const MAIL_TEMPLATE_FRAME = {frame: '199',index: 'a'};
|
||||
|
||||
/* The page that has our echomail area summary template */
|
||||
const MAIL_TEMPLATE_AREA_SUMMARY = {frame: '198',index: 'a'};
|
||||
|
||||
// The maximum size (length) of embedded dynamic fields in frames
|
||||
const DYNAMIC_FIELD_SIZE_MAX = 50;
|
||||
|
||||
/** ESCAPE CODES **/
|
||||
const ESC = '\x1b';
|
||||
|
||||
const FIELD_PASSWORD_MASK = '*';
|
||||
const FIELD_TEXT = 't';
|
||||
const FIELD_PASSWORD = 'p';
|
549
load/funcs.js
Normal file
549
load/funcs.js
Normal file
@ -0,0 +1,549 @@
|
||||
'use strict';
|
||||
require('ansitex/load/defs.js','ANSITEX_HOME'); // ANSITEX definitions
|
||||
|
||||
// Array of page owners
|
||||
var pageowners = [];
|
||||
|
||||
// String repeat.
|
||||
if (!String.prototype.repeat) {
|
||||
String.prototype.repeat = function(count) {
|
||||
'use strict';
|
||||
if (this === null) {
|
||||
throw new TypeError('can\'t convert ' + this + ' to object');
|
||||
}
|
||||
var str = '' + this;
|
||||
count = +count;
|
||||
if (count !== count) {
|
||||
count = 0;
|
||||
}
|
||||
if (count < 0) {
|
||||
throw new RangeError('repeat count must be non-negative: '+count);
|
||||
}
|
||||
if (count === Infinity) {
|
||||
throw new RangeError('repeat count must be less than infinity');
|
||||
}
|
||||
count = Math.floor(count);
|
||||
if (str.length === 0 || count === 0) {
|
||||
return '';
|
||||
}
|
||||
// Ensuring count is a 31-bit integer allows us to heavily optimize the
|
||||
// main part. But anyway, most current (August 2014) browsers can't handle
|
||||
// strings 1 << 28 chars or longer, so:
|
||||
if (str.length * count >= 1 << 28) {
|
||||
throw new RangeError('repeat count must not overflow maximum string size');
|
||||
}
|
||||
var rpt = '';
|
||||
for (;;) {
|
||||
if ((count & 1) === 1) {
|
||||
rpt += str;
|
||||
}
|
||||
count >>>= 1;
|
||||
if (count === 0) {
|
||||
break;
|
||||
}
|
||||
str += str;
|
||||
}
|
||||
return rpt;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert ANSI into BIN for loading into a Frame
|
||||
*
|
||||
* @param ansi
|
||||
*/
|
||||
function ans2bin(ansi,frame) {
|
||||
var x = new Graphic;
|
||||
x.ANSI = ansi;
|
||||
|
||||
var o = 0; // offset into 'bin'
|
||||
for (var yy = 0; yy < 22; yy++) {
|
||||
for (var xx = 0; xx < 80; xx++) {
|
||||
frame.setData(
|
||||
xx,
|
||||
yy,
|
||||
x.BIN.substr(o,1),
|
||||
x.BIN.substr(o+1,1).charCodeAt(0) || BG_BLACK
|
||||
);
|
||||
|
||||
o = o+2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic Field Processing
|
||||
*
|
||||
* @param field field we want to get a value for
|
||||
* @param length length to fill this field
|
||||
* @param pad the padding character if field is less than length
|
||||
* @param context a context value to determine the value from
|
||||
* @returns {string|*|null}
|
||||
* @note bbs.atcodes() cannot process modifiers, so this function is a replacement.
|
||||
*/
|
||||
function atcode(field,length,pad,context) {
|
||||
pad = pad ? pad : ' ';
|
||||
var result = {};
|
||||
var args = [];
|
||||
|
||||
if (field.search(/:/)) {
|
||||
args = field.split(':');
|
||||
field = args.shift();
|
||||
}
|
||||
|
||||
log(LOG_DEBUG,'Field:'+field,'Args:'+JSON.stringify(args));
|
||||
|
||||
switch(field) {
|
||||
// Work out the key for the message area
|
||||
case 'msg_area_key':
|
||||
var index = args.shift();
|
||||
|
||||
log(LOG_DEBUG,'Context:'+context+', Index:'+index);
|
||||
|
||||
// If the message area exists, we'll return a key and a index
|
||||
if (new MsgAreas().getArea(context+index.padStart(2,'0'))) {
|
||||
result.value = index;
|
||||
result.key = '1'+context+index.padStart(2,'0');
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// Get the ECHOAREA Internal Code
|
||||
case 'msg_area_tag':
|
||||
var index = args.shift();
|
||||
var area = new MsgAreas().getArea(context+index.padStart(2,'0'));
|
||||
|
||||
log(LOG_DEBUG,'Context:'+context+', Index:'+index+', Area:'+JSON.stringify(area));
|
||||
|
||||
// If the message area exists
|
||||
if (area)
|
||||
result.value = area.code.toUpperCase();
|
||||
|
||||
break;
|
||||
|
||||
// Get the ECHOAREA Name
|
||||
case 'msg_area_name':
|
||||
var index = args.shift();
|
||||
var area = new MsgAreas().getArea(context+index.padStart(2,'0'));
|
||||
|
||||
if (area)
|
||||
result.value = area.area_name;
|
||||
|
||||
break;
|
||||
|
||||
// Oldest message in msgarea
|
||||
// Our oldest message, is the first message with a tag from the headers
|
||||
case 'msg_area_msgoldest_date':
|
||||
if (typeof context !== 'object') {
|
||||
log(LOG_ERROR,'Unable to render ['+field+'], no context provided');
|
||||
break;
|
||||
}
|
||||
|
||||
var x = context.list_tagged[0];
|
||||
|
||||
result.value = x ? x.date : '';
|
||||
break;
|
||||
|
||||
case 'msg_area_msgoldest_page':
|
||||
if (typeof context !== 'object') {
|
||||
log(LOG_ERROR,'Unable to render ['+field+'], no context provided');
|
||||
break;
|
||||
}
|
||||
|
||||
var x = context.list_tagged[0];
|
||||
|
||||
return x ? context.getMessagePage(x.number) : null;
|
||||
|
||||
// Newest message in msgarea
|
||||
// Our newest message, is the last message with a tag from the headers
|
||||
case 'msg_area_msgnewest_date':
|
||||
if (typeof context !== 'object') {
|
||||
log(LOG_ERROR,'Unable to render ['+field+'], no context provided');
|
||||
break;
|
||||
}
|
||||
|
||||
var x = context.list_tagged[context.list_tagged.length-1];
|
||||
|
||||
result.value = x ? x.date : '';
|
||||
break;
|
||||
|
||||
case 'msg_area_msgnewest_page':
|
||||
if (typeof context !== 'object') {
|
||||
log(LOG_ERROR,'Unable to render ['+field+'], no context provided');
|
||||
break;
|
||||
}
|
||||
|
||||
var x = context.list_tagged[context.list_tagged.length-1];
|
||||
|
||||
return x ? context.getMessagePage(x.number) : null;
|
||||
|
||||
// First unread message
|
||||
case 'msg_area_msgunread_date':
|
||||
if (typeof context !== 'object') {
|
||||
log(LOG_ERROR,'Unable to render ['+field+'], no context provided');
|
||||
break;
|
||||
}
|
||||
|
||||
var x = context.newMsgs();
|
||||
|
||||
result.value = x.length ? x.shift().date : '';
|
||||
break;
|
||||
|
||||
case 'msg_area_msgunread_page':
|
||||
if (typeof context !== 'object') {
|
||||
log(LOG_ERROR,'Unable to render ['+field+'], no context provided');
|
||||
break;
|
||||
}
|
||||
|
||||
var x = context.newMsgs();
|
||||
return x.length ? context.getMessagePage(x.shift().number) : null;
|
||||
|
||||
// First unread message to me
|
||||
case 'msg_area_msgotome_date':
|
||||
if (typeof context !== 'object') {
|
||||
log(LOG_ERROR,'Unable to render ['+field+'], no context provided');
|
||||
break;
|
||||
}
|
||||
|
||||
var x = context.newMsgsToMe();
|
||||
|
||||
result.value = x.length > 1 ? x[1].date : '';
|
||||
break;
|
||||
|
||||
case 'msg_area_msgtome_page':
|
||||
if (typeof context !== 'object') {
|
||||
log(LOG_ERROR,'Unable to render ['+field+'], no context provided');
|
||||
break;
|
||||
}
|
||||
|
||||
var x = context.newMsgsToMe();
|
||||
return x.length > 1 ? context.getMessagePage(x[1].number) : null;
|
||||
|
||||
// Count of unread messages
|
||||
case 'msg_area_new':
|
||||
if (args.length === 1) {
|
||||
context = new MsgArea();
|
||||
context.code = args[0];
|
||||
}
|
||||
|
||||
if (typeof context !== 'object') {
|
||||
log(LOG_ERROR,'Unable to render ['+field+'], no context provided');
|
||||
break;
|
||||
}
|
||||
|
||||
result.value = ''+context.newMsgs().length;
|
||||
break;
|
||||
|
||||
// Count of unread messages to me
|
||||
case 'msg_area_newtome':
|
||||
if (typeof context !== 'object') {
|
||||
log(LOG_ERROR,'Unable to render ['+field+'], no context provided');
|
||||
break;
|
||||
}
|
||||
|
||||
result.value = ''+(context.newMsgsToMe().length > 1 ? context.newMsgsToMe().length-1 : 0);
|
||||
break;
|
||||
|
||||
// Is this message area in my new scan list
|
||||
case 'msg_area_newscan':
|
||||
if (typeof context !== 'object') {
|
||||
log(LOG_ERROR,'Unable to render ['+field+'], no context provided');
|
||||
break;
|
||||
}
|
||||
|
||||
result.value = (context.getUserStats().scan_ptr & SCAN_CFG_TOYOU) ? 'YES' : 'NO';
|
||||
break;
|
||||
|
||||
// Is this message area in my new scan list
|
||||
case 'msg_area_pending':
|
||||
if (typeof context !== 'object') {
|
||||
log(LOG_ERROR,'Unable to render ['+field+'], no context provided');
|
||||
break;
|
||||
}
|
||||
|
||||
result.value = ''+context.list_untagged.length;
|
||||
break;
|
||||
|
||||
// Get the ECHOAREA Total Number of Messages
|
||||
case 'msg_area_total':
|
||||
if (typeof context !== 'object') {
|
||||
log(LOG_ERROR,'Unable to render ['+field+'], no context provided');
|
||||
break;
|
||||
}
|
||||
|
||||
result.value = ''+context.msgbase.total_msgs;
|
||||
break;
|
||||
|
||||
// Get the ECHOAREA Group Name
|
||||
case 'msg_grp_name':
|
||||
if (typeof context !== 'object') {
|
||||
log(LOG_ERROR,'Unable to render ['+field+'], no context provided');
|
||||
break;
|
||||
}
|
||||
|
||||
result.value = context.zone_name;
|
||||
break;
|
||||
|
||||
case 'nodeid':
|
||||
result.value = getNodeID();
|
||||
break;
|
||||
|
||||
default:
|
||||
result.value = (typeof bbs === 'undefined') ? '*'.repeat(Math.abs(length)) : bbs.atcode(field+(args.length ? ':'+args : ''));
|
||||
}
|
||||
|
||||
if ((result.value === null) || (result.value === undefined))
|
||||
result.value = '';
|
||||
|
||||
length = length ? length : result.value.length;
|
||||
|
||||
//log(LOG_DEBUG,' - result length ['+result.length+'] desired ('+length+')');
|
||||
if (result.value.length < Math.abs(length))
|
||||
result.value = (length < 0) ? padright(result.value,Math.abs(length),pad) : padleft(result.value,length,pad);
|
||||
else if (result.value.length > Math.abs(length))
|
||||
result.value = result.value.substr(0,Math.abs(length));
|
||||
|
||||
log(LOG_DEBUG,'* ATCODE ['+field+'] ('+length+'|"'+pad+'") returns ['+result.value+'] key ['+result.key+']');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a message base by code
|
||||
*
|
||||
* @param code
|
||||
* @returns {number | string|boolean}
|
||||
* @deprecated Can this move to the msgbases.js?
|
||||
*/
|
||||
function findMsgBase(code) {
|
||||
if (! code)
|
||||
code = "vtx_data";
|
||||
|
||||
for (var s in msg_area.sub) {
|
||||
var sub = msg_area.sub[s];
|
||||
writeln('sub:'+sub.code);
|
||||
|
||||
if (sub.code.substr(-code.length).toLowerCase() === code)
|
||||
return sub;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an argument from argv, or an error if it doesnt exit
|
||||
*
|
||||
* @param key
|
||||
* @param error
|
||||
* @param abort
|
||||
*/
|
||||
function getArg(key,error,abort) {
|
||||
index = argv.indexOf(key);
|
||||
|
||||
if ((index !== -1) && (! (argv[index+1] === undefined || argv[index+1].match(/^-/)))) {
|
||||
return argv[index+1];
|
||||
}
|
||||
|
||||
if (abort) {
|
||||
log(LOG_ERROR,error);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current node number
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
function getNodeID() {
|
||||
var regex = new RegExp('^'+SYSTEM_ZONE+':');
|
||||
var matches = [];
|
||||
|
||||
for each (var addr in system.fido_addr_list)
|
||||
{
|
||||
if (regex.test(addr)) {
|
||||
addr = addr.replace(regex,'');
|
||||
matches = addr.split('/',2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (matches.length === 0) ? '-' : padright(matches[0],3,'0')+padright(matches[1],3,'0')+padright((typeof bbs === 'undefined') ? 0 : bbs.node_num,2,'0');
|
||||
}
|
||||
|
||||
function getPageOwners() {
|
||||
// Load the owner configuration into memory
|
||||
if (! pageowners.length) {
|
||||
var f = new File(ANSITEX_HOME+'/ctrl/videotex.ini');
|
||||
|
||||
if (f.open('r')) {
|
||||
var logoans = f.iniGetValue('prefix','logoans');
|
||||
var logovtx = f.iniGetValue('prefix','logovtx');
|
||||
var users = f.iniGetValue('prefix','user');
|
||||
//log(LOG_DEBUG,'+ pageOwner: users='+JSON.stringify(users));
|
||||
pageowners.push({prefix: 0,logoans: logoans,logovtx: logovtx,user:users});
|
||||
|
||||
f.iniGetSections('prefix:').forEach(function (prefix) {
|
||||
var p = parseInt(prefix.substr(7));
|
||||
var logoans = f.iniGetValue(prefix,'logoans','');
|
||||
var logovtx = f.iniGetValue(prefix,'logovtx','');
|
||||
var users = f.iniGetValue(prefix,'user','');
|
||||
//log(LOG_DEBUG,'+ pageOwner: users='+JSON.stringify(users));
|
||||
pageowners.push({prefix: p,logoans: logoans,logovtx: logovtx,user:users});
|
||||
});
|
||||
|
||||
} else {
|
||||
log(LOG_DEBUG,'getPageOwners: Couldnt open videotex.ini? :'+JSON.stringify(f));
|
||||
}
|
||||
|
||||
f.close();
|
||||
|
||||
// Sort the pageowners ascending
|
||||
pageowners.sort(function(a,b) { return (a.prefix < b.prefix) ? 1 : ((b.prefix < a.prefix) ? -1 : 0); });
|
||||
|
||||
//log(LOG_DEBUG,'+ pageOwner: pageowners='+JSON.stringify(pageowners));
|
||||
}
|
||||
|
||||
return pageowners;
|
||||
}
|
||||
|
||||
function loadOptions(option) {
|
||||
var f = new File(ANSITEX_HOME+'/ctrl/videotex.ini');
|
||||
|
||||
if (! f.open('r')) {
|
||||
log(LOG_DEBUG,'loadOptions: Couldnt open videotex.ini? :'+JSON.stringify(f));
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
val = f.iniGetObject(option);
|
||||
|
||||
f.close();
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
function msgBaseImport(msgbase,page,text) {
|
||||
var msgbase = new MsgBase(findMsgBase(msgbase).code);
|
||||
|
||||
log(LOG_DEBUG,'Sending ['+page+'] to message base ['+msgbase.cfg.code+']');
|
||||
|
||||
var hdr = { to:'All', from:'Videotex', subject:'Frame: '+page };
|
||||
|
||||
var body = '';
|
||||
body += text+"\r\n";
|
||||
body += "--- " + js.exec_file + " " + '1.0' + "\r\n";
|
||||
|
||||
return msgbase.save_msg(hdr, body);
|
||||
}
|
||||
|
||||
// Right Pad a string with char c
|
||||
function padright(n,width,c) {
|
||||
c = c || '0';
|
||||
n = n + '';
|
||||
return n.length >= width ? n : new Array(width - n.length + 1).join(c) + n;
|
||||
}
|
||||
|
||||
// Left Pad a string with char c
|
||||
function padleft(n,width,c) {
|
||||
c = c || '0';
|
||||
n = n + '';
|
||||
return n.length >= width ? n : n+new Array(width - n.length + 1).join(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the frame as a string
|
||||
*/
|
||||
function pageStr(page) {
|
||||
if (page.frame==null)
|
||||
return null;
|
||||
|
||||
if (! page.index)
|
||||
page.index = 'a';
|
||||
|
||||
return page.frame.toString()+page.index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read our videotex.ini configuration and determine who owns a page.
|
||||
* If there is no prefix for the page, it is owned by the system '0'
|
||||
*
|
||||
* @param page
|
||||
* @returns {undefined}
|
||||
*/
|
||||
function pageOwner(page) {
|
||||
var BreakException = {};
|
||||
var o = null;
|
||||
|
||||
try {
|
||||
getPageOwners().forEach(function(owner) {
|
||||
var p = owner.prefix.toString();
|
||||
o = owner;
|
||||
|
||||
var re = new RegExp('^' + p, 'g');
|
||||
if (page.toString().match(re)) {
|
||||
//log(LOG_DEBUG,'= pageOwner: p='+p+',o: '+o);
|
||||
throw BreakException;
|
||||
}
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
if (e !== BreakException) throw e;
|
||||
}
|
||||
|
||||
//log(LOG_DEBUG,'+ pageOwner: page='+page+', owner: '+JSON.stringify(o));
|
||||
return o;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user edit the frame
|
||||
*
|
||||
* @param page
|
||||
* @param user
|
||||
*/
|
||||
function pageEditor(page) {
|
||||
//log(LOG_DEBUG,'+ pageEditor: page='+page+', user #'+user.number);
|
||||
|
||||
var BreakException = {};
|
||||
var pageditor = false;
|
||||
|
||||
try {
|
||||
getPageOwners().forEach(function(owner) {
|
||||
var p = owner.prefix.toString();
|
||||
//log(LOG_DEBUG,' - pageEditor: '+JSON.stringify(owner));
|
||||
frameusers = owner.user ? owner.user.toString().split(',') : [1];
|
||||
|
||||
log(LOG_DEBUG,' - pageEditor: p='+p+'('+p.length+') user ['+JSON.stringify(frameusers)+'] - :'+frameusers.indexOf(user.number.toString()));
|
||||
|
||||
var re = new RegExp('^' + p, 'g');
|
||||
if (page.toString().match(re) && (frameusers.indexOf(user.number.toString()) !== -1)) {
|
||||
pageditor = true;
|
||||
throw BreakException;
|
||||
}
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
if (e !== BreakException) throw e;
|
||||
}
|
||||
|
||||
log(LOG_DEBUG,'+ pageEditor: page='+page+', editor: '+JSON.stringify(pageditor));
|
||||
return pageditor;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns a list of zones used by this system.
|
||||
*/
|
||||
function zones() {
|
||||
var z = [];
|
||||
var ftn = /([0-9]+):([0-9]+)\/([0-9]+)(\.([0-9]+))?/;
|
||||
|
||||
for(var g=0;g<system.fido_addr_list.length;g++) {
|
||||
z.push(parseInt(system.fido_addr_list[g].match(ftn)[1]));
|
||||
}
|
||||
|
||||
return z.sort(function(a,b) {
|
||||
return (a>b);
|
||||
});
|
||||
}
|
||||
|
||||
this;
|
801
load/msgbases.js
Normal file
801
load/msgbases.js
Normal file
@ -0,0 +1,801 @@
|
||||
'use strict';
|
||||
require('ansitex/load/defs.js','ANSITEX_HOME');
|
||||
|
||||
// Our message bases that are enabled to render Videotex Messages
|
||||
/*
|
||||
* If a message area doesnt have:
|
||||
* + a zone_id, then the group isnt Videotex enabled
|
||||
* + a area_id, then the area isnt Videotex enabled
|
||||
*/
|
||||
function MsgAreas() {
|
||||
this.areas = []; // Message areas
|
||||
var zone_id;
|
||||
var zone_name;
|
||||
var ma;
|
||||
var cfg = [];
|
||||
|
||||
// Load the message areas
|
||||
for (var g in msg_area.grp_list) {
|
||||
for (var a in msg_area.grp_list[g].sub_list) {
|
||||
//writeln('group:'+g+' name:'+msg_area.grp_list[g].name+' FIDO:'+JSON.stringify(msg_area.grp_list[g].sub_list[a].fidonet_addr)+' INDEX:'+JSON.stringify(msg_area.grp_list[g].sub_list[a].ptridx));
|
||||
|
||||
ma = new MsgArea();
|
||||
ma.group_id = g;
|
||||
ma.sub_id = a;
|
||||
|
||||
// Work out the zone by the FTN address of the area
|
||||
if (msg_area.grp_list[g].name.indexOf(':') === -1) {
|
||||
// If the sub is enabled for FTN, and the zone < 9999, then we'll us that if zone_id is undefined
|
||||
if ((msg_area.grp_list[g].sub_list[a].settings & SUB_FIDO) && msg_area.grp_list[g].sub_list[a].fidonet_addr) {
|
||||
var x = msg_area.grp_list[g].sub_list[a].fidonet_addr.split(/([0-9]+):/)[1];
|
||||
if ((x > 0) && (x < 9999)) {
|
||||
zone_id = x.padStart(4,'0');
|
||||
zone_name = msg_area.grp_list[g].name;
|
||||
|
||||
} else {
|
||||
zone_id = undefined;
|
||||
zone_name = undefined;
|
||||
}
|
||||
|
||||
} else {
|
||||
zone_id = undefined;
|
||||
zone_name = undefined;
|
||||
}
|
||||
|
||||
// Work out the zone by the name, where name is zone_id:name
|
||||
} else {
|
||||
zone_id = msg_area.grp_list[g].name.split(':')[0];
|
||||
zone_name = msg_area.grp_list[g].name.split(':')[1];
|
||||
}
|
||||
|
||||
if (zone_id) {
|
||||
if (cfg[zone_id] === undefined) {
|
||||
log(LOG_DEBUG,'Opening internal:'+FRAMES_MSG_BASE+' to work out subs for zone: '+zone_id);
|
||||
var internal = new MsgArea();
|
||||
internal.code = FRAMES_MSG_BASE;
|
||||
|
||||
var conf = internal.get('1'+zone_id+'99999a');
|
||||
|
||||
// @todo Quick hack for scripts
|
||||
if (SESSION_EXT === undefined)
|
||||
var SESSION_EXT = 'tex';
|
||||
|
||||
if (conf !== undefined)
|
||||
cfg[zone_id] = conf.content[SESSION_EXT].content.map(function(item) { return item.toLowerCase(); });
|
||||
else
|
||||
cfg[zone_id] = [];
|
||||
}
|
||||
|
||||
ma.zone_id = zone_id;
|
||||
ma.zone_name = zone_name;
|
||||
ma.code = msg_area.grp_list[g].sub_list[a].code;
|
||||
|
||||
// Our area_id can be embedded in the name of the area
|
||||
if (msg_area.grp_list[g].sub_list[a].name.indexOf(':') !== -1) {
|
||||
var sublist = msg_area.grp_list[g].sub_list[a].name.split(':');
|
||||
ma.area_id = sublist[0];
|
||||
|
||||
/*
|
||||
writeln(' code:'+ma.code);
|
||||
writeln(' fullname:'+ma.full_name);
|
||||
writeln(' pageprefix:'+ma.page_prefix)
|
||||
writeln();
|
||||
*/
|
||||
log(LOG_DEBUG,'Hard set index to ['+ma.area_id+'] for: '+ma.code);
|
||||
|
||||
// Otherwise we get our area_id from the configuration - page 999 of the zone
|
||||
} else {
|
||||
var index = (cfg[zone_id].indexOf(msg_area.grp_list[g].sub_list[a].code)+1).toString().padStart(2,'0');
|
||||
|
||||
// Make sure that index isnt already defined
|
||||
if (this.areas.filter(function(item) { return (item.zone_id === zone_id) && (item.area_id === index); }).length) {
|
||||
log(LOG_ERROR,'! ERROR: Prefix ['+index+'] already defined in ['+zone_id+'], ignoring ['+ma.code+']');
|
||||
|
||||
} else if ((cfg[zone_id] !== undefined) && cfg[zone_id].length) {
|
||||
if (index === '00')
|
||||
ma.area_id = undefined;
|
||||
else
|
||||
ma.area_id = index;
|
||||
}
|
||||
}
|
||||
|
||||
ma.area_name = msg_area.grp_list[g].sub_list[a].description;
|
||||
|
||||
this.areas.push(ma);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* List areas that we do and dont manage */
|
||||
Object.defineProperty(this,'list',{
|
||||
get: function() {
|
||||
var areas = this.managed;
|
||||
writeln('Areas that we ARE managing mail:'+areas.length);
|
||||
for (var x in areas.sort(function(a,b) { return a.page_prefix > b.page_prefix; })) {
|
||||
writeln(x+':'+areas[x].code+' prefix:'+areas[x].page_prefix);
|
||||
}
|
||||
|
||||
areas = this.unmanaged;
|
||||
writeln('Areas that we are NOT managing mail:'+areas.length);
|
||||
for (var x in areas)
|
||||
writeln(x+':'+areas[x].code);
|
||||
}
|
||||
});
|
||||
|
||||
/* List of message areas managed */
|
||||
Object.defineProperty(this,'managed',{
|
||||
get: function() {
|
||||
return this.areas.filter(function(item) {
|
||||
return item.managed;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/* List of message areas unmanaged */
|
||||
Object.defineProperty(this,'unmanaged',{
|
||||
get: function() {
|
||||
return this.areas.filter(function(item) {
|
||||
return ! item.managed;
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
/* Fetch a specific message area */
|
||||
MsgAreas.prototype.getArea = function(area) {
|
||||
if (area === undefined)
|
||||
return undefined;
|
||||
|
||||
return (((area.length === 6) && (NUMERIC_REGEX.test(area)))
|
||||
? this.areas.filter(function(x) {
|
||||
return x.page_prefix === area;
|
||||
})
|
||||
|
||||
: this.areas.filter(function(x) {
|
||||
return x.code === area;
|
||||
})).pop();
|
||||
}
|
||||
|
||||
// @todo review
|
||||
MsgAreas.prototype.getMessage = function(page) {
|
||||
var area = this.getArea(page);
|
||||
log(LOG_DEBUG,' - msg:'+JSON.stringify(page.substr(7,4)));
|
||||
|
||||
return area ? area.getMessage(page.substr(7,4)) : undefined;
|
||||
}
|
||||
|
||||
// @todo review
|
||||
MsgAreas.prototype.getUserStats = function(page) {
|
||||
var area = this.getArea(page);
|
||||
|
||||
return area ? msg_area.grp_list[area.msgbase.cfg.grp_number].sub_list[msg_area.sub[area.msgbase.cfg.code].index] : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function MsgArea() {
|
||||
this.zone_id = undefined; // Zone this message area belongs to, eg: 0516
|
||||
this.zone_name = undefined; // Zone Name, eg: VIDEOTEX
|
||||
this.area_id = undefined; // Sub Area ID for this message area, eg: 01
|
||||
this.area_name = undefined; // Sub Area Name for this message area, eg: CHAT
|
||||
this.group_id = undefined; // SBBS Message Group ID
|
||||
this.__properties__ = {
|
||||
code: undefined, // SBBS Message Sub Internal Code
|
||||
};
|
||||
|
||||
// MSG Base Code
|
||||
Object.defineProperty(this,'code',{
|
||||
get: function() {
|
||||
return this.__properties__.code;
|
||||
},
|
||||
|
||||
set: function(code) {
|
||||
this.__properties__.code = code;
|
||||
}
|
||||
});
|
||||
|
||||
// Get all frames that are tagged
|
||||
Object.defineProperty(this,'frames',{
|
||||
get: function() {
|
||||
var msgbase;
|
||||
var regex = false;
|
||||
|
||||
if (this.code === FRAMES_MSG_BASE) {
|
||||
msgbase = this.msgbase;
|
||||
|
||||
} else if (this.managed) {
|
||||
msgbase = new MsgBase(FRAMES_MSG_BASE);
|
||||
regex = this.page_prefix_regex;
|
||||
|
||||
} else
|
||||
return undefined;
|
||||
|
||||
var frames = [];
|
||||
|
||||
try {
|
||||
if (msgbase.open()) {
|
||||
var headers = msgbase.get_all_msg_headers(false,false) || [];
|
||||
|
||||
for (var i in headers) {
|
||||
if ((! (headers[i].attr&MSG_DELETE)) && (headers[i].from === 'SYSTEM') && ((! regex) || regex.test(headers[i].to)))
|
||||
frames.push(headers[i]);
|
||||
}
|
||||
|
||||
msgbase.close();
|
||||
|
||||
} else {
|
||||
writeln('*** NOPE WONT OPEN msgbase:'+JSON.stringify(msgbase));
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
log(LOG_ERROR,this.code+' cannot be opened (frames):'+e.message);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return frames.sort(function(a,b) {
|
||||
return (a.when_imported_time !== b.when_imported_time)
|
||||
? a.when_imported_time > b.when_imported_time
|
||||
: a.number > b.number;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get Area's full name
|
||||
Object.defineProperty(this,'full_name',{
|
||||
get: function() {
|
||||
return this.zone_name+':'+this.area_name;
|
||||
}
|
||||
});
|
||||
|
||||
// @deprecated use frames instead
|
||||
// Total tagged messages
|
||||
Object.defineProperty(this,'list_tagged',{
|
||||
get: function() {
|
||||
if (this.tagged_list === undefined) {
|
||||
this.tagged_list = [];
|
||||
|
||||
if (! this.headers)
|
||||
return this.tagged_list;
|
||||
|
||||
for(var x in this.headers) {
|
||||
if (this.headers[x].tags && (this.headers[x].tags.length === PAGE_LENGTH)) {
|
||||
this.tagged_list.push(this.headers[x]);
|
||||
write(); // @todo This is needed for this to work?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.tagged_list;
|
||||
}
|
||||
});
|
||||
|
||||
// Retrieve the last tagged frame
|
||||
Object.defineProperty(this,'last_tagged_message',{
|
||||
get: function() {
|
||||
var last_tag = this.frames.sort(function(a,b) {
|
||||
return (a.when_imported_time !== b.when_imported_time)
|
||||
? a.when_imported_time > b.when_imported_time
|
||||
: a.number > b.number;
|
||||
}).pop();
|
||||
|
||||
if (last_tag === undefined)
|
||||
return undefined;
|
||||
|
||||
var msgbase = new MsgBase(FRAMES_MSG_BASE);
|
||||
|
||||
try {
|
||||
if (msgbase.open()) {
|
||||
var body = JSON.parse(LZString.decompressFromBase64(msgbase.get_msg_body(last_tag.number)));
|
||||
|
||||
msgbase.close();
|
||||
|
||||
} else {
|
||||
writeln('*** NOPE WONT OPEN msgbase:'+JSON.stringify(msgbase));
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
log(LOG_ERROR,this.code+' cannot be opened (last_tagged_message):'+e.message);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
msgbase = this.msgbase;
|
||||
|
||||
try {
|
||||
if (msgbase.open()) {
|
||||
var index = msgbase.get_msg_header(body.id);
|
||||
|
||||
msgbase.close();
|
||||
|
||||
} else {
|
||||
writeln('*** NOPE WONT OPEN msgbase:'+JSON.stringify(msgbase));
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
log(LOG_ERROR,this.code+' cannot be opened (last_tagged_message_base):'+e.message);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
})
|
||||
|
||||
// Is this area defined for ansitex messages
|
||||
Object.defineProperty(this,'managed',{
|
||||
get: function() {
|
||||
return (this.zone_id !== undefined) && (this.area_id !== undefined);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this,'max_page',{
|
||||
get: function() {
|
||||
return parseInt('9'.repeat(PAGE_LENGTH),10);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this,'msgbase',{
|
||||
get: function() {
|
||||
return new MsgBase(this.code);
|
||||
}
|
||||
});
|
||||
|
||||
// Get Next page number
|
||||
Object.defineProperty(this,'page_next',{
|
||||
get: function() {
|
||||
if (! this.managed)
|
||||
return undefined;
|
||||
|
||||
var next_tag = this.frames.pop();
|
||||
|
||||
if (next_tag !== undefined) {
|
||||
next_tag = next_tag.tags;
|
||||
|
||||
if (next_tag.indexOf('1'+this.page_prefix) === 0)
|
||||
next_tag = next_tag.slice(this.page_prefix.length+1);
|
||||
|
||||
next_tag = parseInt(next_tag,10)+1;
|
||||
|
||||
if (next_tag > this.max_page)
|
||||
next_tag = 0;
|
||||
|
||||
} else {
|
||||
next_tag = 0;
|
||||
}
|
||||
|
||||
return (''+next_tag).padStart(4,'0');
|
||||
},
|
||||
|
||||
/*
|
||||
set: function(page) {
|
||||
var f = new File(file_cfgname(system.mods_dir,'ansitex/ctrl/videotex.ini'));
|
||||
if (! f.open('r+')) {
|
||||
writeln('Unable to open ini file');
|
||||
exit(2);
|
||||
}
|
||||
|
||||
f.iniSetValue('prefix:'+this.page_prefix,PAGE_LAST_KEY,(''+page).padStart(4,'0'));
|
||||
f.close();
|
||||
}
|
||||
*/
|
||||
});
|
||||
|
||||
// Our page prefix for this msg area
|
||||
Object.defineProperty(this,'page_prefix',{
|
||||
get: function() {
|
||||
return ''+this.zone_id+this.area_id;
|
||||
},
|
||||
});
|
||||
|
||||
// Return a REG to test if this frame is part of this msgbase
|
||||
Object.defineProperty(this,'page_prefix_regex',{
|
||||
get: function() {
|
||||
return new RegExp( '^1'+this.page_prefix);
|
||||
},
|
||||
});
|
||||
|
||||
// Total Messages in a msgbase
|
||||
Object.defineProperty(this,'total_msgs',{
|
||||
get: function() {
|
||||
if (! this.managed)
|
||||
return undefined;
|
||||
|
||||
var msgbase = this.msgbase;
|
||||
|
||||
try {
|
||||
if (msgbase.open()) {
|
||||
var index = msgbase.get_index() || [];
|
||||
|
||||
msgbase.close();
|
||||
|
||||
} else {
|
||||
writeln('*** NOPE WONT OPEN msgbase:'+JSON.stringify(msgbase));
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
log(LOG_ERROR,this.code+' cannot be opened (total_msgs):'+e.message);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return index.length;
|
||||
}
|
||||
});
|
||||
|
||||
// List untagged messages
|
||||
Object.defineProperty(this,'untagged', {
|
||||
get: function() {
|
||||
if (! this.managed)
|
||||
return undefined;
|
||||
|
||||
var msgbase = this.msgbase;
|
||||
var last_tag = this.last_tagged_message;
|
||||
var msgs = [];
|
||||
|
||||
try {
|
||||
if (msgbase.open()) {
|
||||
var headers = msgbase.get_all_msg_headers(false,false);
|
||||
|
||||
for (var x in headers) {
|
||||
if (last_tag
|
||||
&& ((headers[x].when_imported_time < last_tag.when_imported_time)
|
||||
|| ((headers[x].when_imported_time === last_tag.when_imported_time) && (headers[x].number <= last_tag.number))))
|
||||
continue;
|
||||
|
||||
msgs.push(headers[x]);
|
||||
}
|
||||
|
||||
msgbase.close();
|
||||
|
||||
} else {
|
||||
writeln('*** NOPE WONT OPEN msgbase:'+JSON.stringify(msgbase));
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
log(LOG_ERROR,this.code+' cannot be opened (untagged):'+e.message);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return msgs.sort(function(a,b) {
|
||||
return (a.when_imported_time !== b.when_imported_time)
|
||||
? a.when_imported_time > b.when_imported_time
|
||||
: a.number > b.number;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get message header for page
|
||||
MsgArea.prototype.get = function(page) {
|
||||
if (this.code !== FRAMES_MSG_BASE)
|
||||
return undefined;
|
||||
|
||||
var msgbase = this.msgbase;
|
||||
var msgs = [];
|
||||
|
||||
try {
|
||||
if (msgbase.open()) {
|
||||
log(LOG_DEBUG,'Looking for ['+page+'] in ['+this.code+']');
|
||||
|
||||
var index = msgbase
|
||||
.get_all_msg_headers();
|
||||
|
||||
for (var x in index)
|
||||
if ((index[x].from === 'SYSTEM') && (index[x].to === page))
|
||||
msgs.push(index[x]);
|
||||
|
||||
if (msgs.length) {
|
||||
var msg = msgs.sort(function(a,b) {
|
||||
return (a.when_imported_time !== b.when_imported_time)
|
||||
? a.when_imported_time > b.when_imported_time
|
||||
: a.number > b.number;
|
||||
}).pop();
|
||||
|
||||
return this.getContent(msg.number);
|
||||
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
log(LOG_ERROR,this.code+' cannot be opened (frames):'+e.message);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
// Get frame content
|
||||
MsgArea.prototype.getContent = function(id) {
|
||||
// @todo If this is for a echomail/netmail content, then we need to switch message bases
|
||||
if (this.code !== FRAMES_MSG_BASE)
|
||||
return undefined;
|
||||
|
||||
var msgbase = this.msgbase;
|
||||
|
||||
try {
|
||||
if (msgbase.open()) {
|
||||
var raw = msgbase.get_msg_body(false,id,false,false,true,true);
|
||||
//log(LOG_DEBUG,'RAW:'+JSON.stringify(raw));
|
||||
|
||||
// Our messages are terminated with FRAMES_EOF_MARKER
|
||||
var regex = new RegExp('^(.*)'+FRAMES_EOF_MARKER);
|
||||
//log(LOG_DEBUG,'MARKER:'+regex.test(raw));
|
||||
if (! regex.test(raw)) {
|
||||
msgbase.close();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var regex = new RegExp(FRAMES_EOF_MARKER+'[.\\s\\S]*$');
|
||||
|
||||
var content = JSON.parse(LZString.decompressFromBase64(raw.replace(regex,'')));
|
||||
//log(LOG_DEBUG,'CONTENT:'+JSON.stringify(content));
|
||||
msgbase.close();
|
||||
|
||||
} else {
|
||||
writeln('*** NOPE WONT OPEN msgbase:'+JSON.stringify(msgbase));
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
log(LOG_ERROR,this.code+' cannot be opened (frames):'+e.message);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return content;
|
||||
};
|
||||
|
||||
// @todo review
|
||||
/**
|
||||
* Unread messages [1..]
|
||||
* Array key 0 returns the last read message
|
||||
*
|
||||
* @returns {*[]}
|
||||
*/
|
||||
MsgArea.prototype.newMsgs = function() {
|
||||
var msgs = [];
|
||||
var stats = this.getUserStats();
|
||||
//log(LOG_DEBUG,'Users last_read pointer: '+JSON.stringify(stats.last_read));
|
||||
|
||||
for(var x in this.list_tagged) {
|
||||
// Advance past our last scan_ptr
|
||||
if (this.list_tagged[x].number <= stats.last_read)
|
||||
continue;
|
||||
|
||||
msgs.push(this.list_tagged[x]);
|
||||
|
||||
write(); // @todo This is needed for this to work?
|
||||
}
|
||||
|
||||
return msgs;
|
||||
}
|
||||
|
||||
// @todo review
|
||||
/**
|
||||
* New Messages for the logged in user
|
||||
*/
|
||||
MsgArea.prototype.newMsgsToMe = function() {
|
||||
var msgs = [];
|
||||
var stats = this.getUserStats();
|
||||
var last = null;
|
||||
//log(LOG_DEBUG,'Users scan_ptr pointer: '+JSON.stringify(stats.scan_ptr));
|
||||
|
||||
for(var x in this.list_tagged) {
|
||||
// Advance past our last scan_ptr
|
||||
if (this.list_tagged[x].number <= stats.scan_ptr) {
|
||||
if ((this.list_tagged[x].to === user.name) || (this.list_tagged[x].to === user.alias))
|
||||
last = x;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add our previous to me message
|
||||
if (msgs.length === 0)
|
||||
msgs.push(last !== null ? this.list_tagged[last] : []);
|
||||
|
||||
if ((this.list_tagged[x].to === user.name) || (this.list_tagged[x].to === user.alias))
|
||||
msgs.push(this.list_tagged[x]);
|
||||
|
||||
write(); // @todo This is needed for this to work?
|
||||
}
|
||||
|
||||
return msgs;
|
||||
}
|
||||
|
||||
// @todo review
|
||||
/**
|
||||
* Get a specific message with a tag
|
||||
*/
|
||||
MsgArea.prototype.getMessage = function(page) {
|
||||
var msg = undefined;
|
||||
|
||||
for(var x in this.list_tagged) {
|
||||
if (this.list_tagged[x].tags === page) {
|
||||
msg = this.list_tagged[x];
|
||||
break;
|
||||
}
|
||||
write(); // @todo This is needed for this to work?
|
||||
}
|
||||
|
||||
if (! msg)
|
||||
return undefined;
|
||||
|
||||
if (! this.msgbase.open()) {
|
||||
writeln(code+' cannot be opened (getMessage):'+this.msgbase.error);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
msg.grp_number = this.msgbase.cfg.grp_number;
|
||||
var cfg = this.msgbase.cfg;
|
||||
msg.subnum = msg_area.grp_list[cfg.grp_number].sub_list.filter(function(x) { return x.number === cfg.number; }).pop().index;
|
||||
msg.content = this.msgbase.get_msg_body(false,msg.number,false,false,true,true);
|
||||
this.msgbase.close();
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
// @todo review
|
||||
/**
|
||||
* Get a message page by pointer
|
||||
*
|
||||
* @param number
|
||||
* @returns {string}
|
||||
*/
|
||||
MsgArea.prototype.getMessagePage = function(number) {
|
||||
log(LOG_DEBUG,'Get Message Page with number ['+number+']');
|
||||
|
||||
var r;
|
||||
|
||||
for (var x in this.list_tagged) {
|
||||
if (this.list_tagged[x].number === number) {
|
||||
r = this.list_tagged[x];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! r || r.tags === undefined)
|
||||
return null;
|
||||
|
||||
return '1'+this.zone_id+this.area_id+r.tags;
|
||||
}
|
||||
|
||||
// @todo review
|
||||
MsgArea.prototype.getUserStats = function() {
|
||||
return this.msgbase.cfg ? msg_area.grp_list[this.msgbase.cfg.grp_number].sub_list[msg_area.sub[this.msgbase.cfg.code].index] : [];
|
||||
}
|
||||
|
||||
// @todo review
|
||||
MsgArea.prototype.MessageNext = function(page) {
|
||||
var x = null;
|
||||
|
||||
if (! page)
|
||||
return undefined;
|
||||
|
||||
var msgid = page.substr(7,4);
|
||||
|
||||
for(var x in this.list_tagged) {
|
||||
if (this.list_tagged[x].tags === msgid) {
|
||||
break;
|
||||
}
|
||||
|
||||
write(); // @todo This is needed for this to work?
|
||||
}
|
||||
|
||||
//log(LOG_DEBUG,'- Next Message is:'+JSON.stringify(this.list_tagged[(parseInt(x)+1)])+', msgid:'+msgid+', page:'+page+', x:'+x);
|
||||
|
||||
/*
|
||||
= Our next message is either
|
||||
+ x+1 if x < this.list_tagged.length
|
||||
+ x=0 if x == this.list_tagged.length (-1)
|
||||
+ null if this.list_tagged.length == null; (thus no messages)
|
||||
*/
|
||||
|
||||
return x === null ? null : this.list_tagged[(parseInt(x) === this.list_tagged.length-1) ? 0 : (parseInt(x)+1)];
|
||||
}
|
||||
|
||||
// @todo review
|
||||
MsgArea.prototype.MessagePrev = function(page) {
|
||||
var prev = null;
|
||||
var x = null;
|
||||
|
||||
if (! page)
|
||||
return undefined;
|
||||
|
||||
var msgid = page.substr(7,4);
|
||||
|
||||
for(var x in this.list_tagged) {
|
||||
if (this.list_tagged[x].tags === msgid) {
|
||||
break;
|
||||
|
||||
} else {
|
||||
prev = x;
|
||||
}
|
||||
|
||||
write(); // @todo This is needed for this to work?
|
||||
}
|
||||
|
||||
/*
|
||||
= Our previous message is either
|
||||
+ prev if a tag was found, unless
|
||||
+ prev is null, in which case it is this.list_tagged.length -1
|
||||
+ null if x is still null (thus no messages)
|
||||
*/
|
||||
|
||||
// If prev is still null, then our last message must be the last one, unless x is null then there are no messages
|
||||
return x === null ? null : this.list_tagged[(prev === null) ? this.list_tagged.length-1 : parseInt(prev)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag messages with a frame number
|
||||
* @note: May need to run jsexec with -m 32MB to overcome memory issues
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
MsgArea.prototype.tag_msgs = function() {
|
||||
var msgs = this.untagged;
|
||||
var msgbase = new MsgBase(FRAMES_MSG_BASE);
|
||||
var page_next = this.page_next;
|
||||
|
||||
writeln('* We have '+msgs.length+' messages to tag, starting at '+page_next);
|
||||
|
||||
// See if we need to tag something
|
||||
if (! msgs.length)
|
||||
return;
|
||||
|
||||
if (! msgbase.open()) {
|
||||
writeln(code+' cannot be opened (tag_msgs):'+msgbase.error);
|
||||
return false;
|
||||
}
|
||||
|
||||
writeln('Starting at:'+page_next+' (max:'+this.max_page+')');
|
||||
|
||||
for (var x in msgs) {
|
||||
//writeln('- '+msgs[x].when_imported_time+', #:'+msgs[x].number);
|
||||
|
||||
var frame = '1'+this.page_prefix+page_next;
|
||||
|
||||
var hdr = {
|
||||
to: frame+'a',
|
||||
from: 'SYSTEM',
|
||||
tags: frame,
|
||||
date: msgs[x].date,
|
||||
subject: this.code+':'+msgs[x].id,
|
||||
};
|
||||
|
||||
var page = {
|
||||
id: msgs[x].number,
|
||||
area_id: this.area_id,
|
||||
group_id: this.group_id,
|
||||
date: msgs[x].date,
|
||||
msgid: msgs[x].id,
|
||||
imported: msgs[x].when_imported_time,
|
||||
};
|
||||
|
||||
var body = LZString.compressToBase64(JSON.stringify(page));
|
||||
|
||||
writeln('Tagging:'+page.id+' Tag:'+page_next+' MSGID:'+page.msgid);
|
||||
|
||||
if (! msgbase.save_msg(hdr,body)) {
|
||||
writeln('! ERROR: Failed to tag '+msgs[x].number);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
page_next++;
|
||||
if (page_next > this.max_page)
|
||||
page_next = 0;
|
||||
|
||||
page_next = (''+page_next).padStart(4,'0');
|
||||
}
|
||||
|
||||
msgbase.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// @todo review
|
||||
MsgArea.prototype.page = function(msgid) {
|
||||
return '1'+this.page_prefix+msgid;
|
||||
}
|
||||
}
|
1441
load/page.js
Normal file
1441
load/page.js
Normal file
File diff suppressed because it is too large
Load Diff
995
load/qrcode-make.js
Normal file
995
load/qrcode-make.js
Normal file
@ -0,0 +1,995 @@
|
||||
'use strict';
|
||||
/*
|
||||
* Module "qrcodegen", public members:
|
||||
* - Class QrCode:
|
||||
* - Function encodeText(str text, QrCode.Ecc ecl) -> QrCode
|
||||
* - Function encodeBinary(list<byte> data, QrCode.Ecc ecl) -> QrCode
|
||||
* - Function encodeSegments(list<QrSegment> segs, QrCode.Ecc ecl,
|
||||
* int minVersion=1, int maxVersion=40, mask=-1, boostEcl=true) -> QrCode
|
||||
* - Constants int MIN_VERSION, MAX_VERSION
|
||||
* - Constructor QrCode(int version, QrCode.Ecc ecl, list<byte> dataCodewords, int mask)
|
||||
* - Fields int version, size, mask
|
||||
* - Field QrCode.Ecc errorCorrectionLevel
|
||||
* - Method getModule(int x, int y) -> bool
|
||||
* - Method drawCanvas(int scale, int border, HTMLCanvasElement canvas) -> void
|
||||
* - Method toSvgString(int border) -> str
|
||||
* - Enum Ecc:
|
||||
* - Constants LOW, MEDIUM, QUARTILE, HIGH
|
||||
* - Field int ordinal
|
||||
* - Class QrSegment:
|
||||
* - Function makeBytes(list<byte> data) -> QrSegment
|
||||
* - Function makeNumeric(str data) -> QrSegment
|
||||
* - Function makeAlphanumeric(str data) -> QrSegment
|
||||
* - Function makeSegments(str text) -> list<QrSegment>
|
||||
* - Function makeEci(int assignVal) -> QrSegment
|
||||
* - Constructor QrSegment(QrSegment.Mode mode, int numChars, list<int> bitData)
|
||||
* - Field QrSegment.Mode mode
|
||||
* - Field int numChars
|
||||
* - Method getData() -> list<int>
|
||||
* - Constants RegExp NUMERIC_REGEX, ALPHANUMERIC_REGEX
|
||||
* - Enum Mode:
|
||||
* - Constants NUMERIC, ALPHANUMERIC, BYTE, KANJI, ECI
|
||||
*/
|
||||
var qrcodegen = new function() {
|
||||
this.QrCode = function(version, errCorLvl, dataCodewords, mask) {
|
||||
|
||||
/*---- Constructor (low level) ----*/
|
||||
|
||||
// Check scalar arguments
|
||||
if (version < MIN_VERSION || version > MAX_VERSION)
|
||||
throw 'Version value out of range';
|
||||
if (mask < -1 || mask > 7)
|
||||
throw 'Mask value out of range';
|
||||
if (!(errCorLvl instanceof Ecc))
|
||||
throw 'QrCode.Ecc expected';
|
||||
var size = version * 4 + 17;
|
||||
|
||||
// Initialize both grids to be size*size arrays of Boolean false
|
||||
var row = [];
|
||||
for (var i = 0; i < size; i++)
|
||||
row.push(false);
|
||||
var modules = []; // Initially all white
|
||||
var isFunction = [];
|
||||
for (var i = 0; i < size; i++) {
|
||||
modules .push(row.slice());
|
||||
isFunction.push(row.slice());
|
||||
}
|
||||
|
||||
// Compute ECC, draw modules
|
||||
drawFunctionPatterns();
|
||||
var allCodewords = addEccAndInterleave(dataCodewords);
|
||||
drawCodewords(allCodewords);
|
||||
|
||||
// Do masking
|
||||
if (mask === -1) { // Automatically choose best mask
|
||||
var minPenalty = Infinity;
|
||||
for (var i = 0; i < 8; i++) {
|
||||
applyMask(i);
|
||||
drawFormatBits(i);
|
||||
var penalty = getPenaltyScore();
|
||||
if (penalty < minPenalty) {
|
||||
mask = i;
|
||||
minPenalty = penalty;
|
||||
}
|
||||
applyMask(i); // Undoes the mask due to XOR
|
||||
}
|
||||
}
|
||||
if (mask < 0 || mask > 7)
|
||||
throw 'Assertion error';
|
||||
applyMask(mask); // Apply the final choice of mask
|
||||
drawFormatBits(mask); // Overwrite old format bits
|
||||
|
||||
isFunction = null;
|
||||
|
||||
|
||||
/*---- Read-only instance properties ----*/
|
||||
|
||||
// The version number of this QR Code, which is between 1 and 40 (inclusive).
|
||||
// This determines the size of this barcode.
|
||||
Object.defineProperty(this, 'version', {value:version});
|
||||
|
||||
// The width and height of this QR Code, measured in modules, between
|
||||
// 21 and 177 (inclusive). This is equal to version * 4 + 17.
|
||||
Object.defineProperty(this, 'size', {value:size});
|
||||
|
||||
// The error correction level used in this QR Code.
|
||||
Object.defineProperty(this, 'errorCorrectionLevel', {value:errCorLvl});
|
||||
|
||||
// The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive).
|
||||
// Even if a QR Code is created with automatic masking requested (mask = -1),
|
||||
// the resulting object still has a mask value between 0 and 7.
|
||||
Object.defineProperty(this, 'mask', {value:mask});
|
||||
|
||||
|
||||
/*---- Accessor methods ----*/
|
||||
|
||||
// Returns the color of the module (pixel) at the given coordinates, which is false
|
||||
// for white or true for black. The top left corner has the coordinates (x=0, y=0).
|
||||
// If the given coordinates are out of bounds, then false (white) is returned.
|
||||
this.getModule = function(x, y) {
|
||||
return 0 <= x && x < size && 0 <= y && y < size && modules[y][x];
|
||||
};
|
||||
|
||||
|
||||
/*---- Public instance methods ----*/
|
||||
|
||||
// Draws this QR Code, with the given module scale and border modules, onto the given HTML
|
||||
// canvas element. The canvas's width and height is resized to (this.size + border * 2) * scale.
|
||||
// The drawn image is be purely black and white, and fully opaque.
|
||||
// The scale must be a positive integer and the border must be a non-negative integer.
|
||||
this.drawCanvas = function(scale, border, canvas) {
|
||||
if (scale <= 0 || border < 0)
|
||||
throw 'Value out of range';
|
||||
var width = (size + border * 2) * scale;
|
||||
canvas.width = width;
|
||||
canvas.height = width;
|
||||
var ctx = canvas.getContext('2d');
|
||||
for (var y = -border; y < size + border; y++) {
|
||||
for (var x = -border; x < size + border; x++) {
|
||||
ctx.fillStyle = this.getModule(x, y) ? '#000000' : '#FFFFFF';
|
||||
ctx.fillRect((x + border) * scale, (y + border) * scale, scale, scale);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Returns a string of SVG code for an image depicting this QR Code, with the given number
|
||||
// of border modules. The string always uses Unix newlines (\n), regardless of the platform.
|
||||
this.toSvgString = function(border) {
|
||||
if (border < 0)
|
||||
throw 'Border must be non-negative';
|
||||
var parts = [];
|
||||
for (var y = 0; y < size; y++) {
|
||||
for (var x = 0; x < size; x++) {
|
||||
if (this.getModule(x, y))
|
||||
parts.push('M' + (x + border) + ',' + (y + border) + 'h1v1h-1z');
|
||||
}
|
||||
}
|
||||
return '<?xml version="1.0" encoding="UTF-8"?>\n' +
|
||||
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 ' +
|
||||
(size + border * 2) + ' ' + (size + border * 2) + '" stroke="none">\n' +
|
||||
'\t<rect width="100%" height="100%" fill="#FFFFFF"/>\n' +
|
||||
'\t<path d="' + parts.join(" ") + '" fill="#000000"/>\n' +
|
||||
'</svg>\n';
|
||||
};
|
||||
|
||||
|
||||
/*---- Private helper methods for constructor: Drawing function modules ----*/
|
||||
|
||||
// Reads this object's version field, and draws and marks all function modules.
|
||||
function drawFunctionPatterns() {
|
||||
// Draw horizontal and vertical timing patterns
|
||||
for (var i = 0; i < size; i++) {
|
||||
setFunctionModule(6, i, i % 2 === 0);
|
||||
setFunctionModule(i, 6, i % 2 === 0);
|
||||
}
|
||||
|
||||
// Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)
|
||||
drawFinderPattern(3, 3);
|
||||
drawFinderPattern(size - 4, 3);
|
||||
drawFinderPattern(3, size - 4);
|
||||
|
||||
// Draw numerous alignment patterns
|
||||
var alignPatPos = getAlignmentPatternPositions();
|
||||
var numAlign = alignPatPos.length;
|
||||
for (var i = 0; i < numAlign; i++) {
|
||||
for (var j = 0; j < numAlign; j++) {
|
||||
// Don't draw on the three finder corners
|
||||
if (!(i === 0 && j === 0 || i === 0 && j === numAlign - 1 || i === numAlign - 1 && j === 0))
|
||||
drawAlignmentPattern(alignPatPos[i], alignPatPos[j]);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw configuration data
|
||||
drawFormatBits(0); // Dummy mask value; overwritten later in the constructor
|
||||
drawVersion();
|
||||
}
|
||||
|
||||
|
||||
// Draws two copies of the format bits (with its own error correction code)
|
||||
// based on the given mask and this object's error correction level field.
|
||||
function drawFormatBits(mask) {
|
||||
// Calculate error correction code and pack bits
|
||||
var data = errCorLvl.formatBits << 3 | mask; // errCorrLvl is uint2, mask is uint3
|
||||
var rem = data;
|
||||
for (var i = 0; i < 10; i++)
|
||||
rem = (rem << 1) ^ ((rem >>> 9) * 0x537);
|
||||
var bits = (data << 10 | rem) ^ 0x5412; // uint15
|
||||
if (bits >>> 15 !== 0)
|
||||
throw 'Assertion error';
|
||||
|
||||
// Draw first copy
|
||||
for (var i = 0; i <= 5; i++)
|
||||
setFunctionModule(8, i, getBit(bits, i));
|
||||
setFunctionModule(8, 7, getBit(bits, 6));
|
||||
setFunctionModule(8, 8, getBit(bits, 7));
|
||||
setFunctionModule(7, 8, getBit(bits, 8));
|
||||
for (var i = 9; i < 15; i++)
|
||||
setFunctionModule(14 - i, 8, getBit(bits, i));
|
||||
|
||||
// Draw second copy
|
||||
for (var i = 0; i < 8; i++)
|
||||
setFunctionModule(size - 1 - i, 8, getBit(bits, i));
|
||||
for (var i = 8; i < 15; i++)
|
||||
setFunctionModule(8, size - 15 + i, getBit(bits, i));
|
||||
setFunctionModule(8, size - 8, true); // Always black
|
||||
}
|
||||
|
||||
|
||||
// Draws two copies of the version bits (with its own error correction code),
|
||||
// based on this object's version field, iff 7 <= version <= 40.
|
||||
function drawVersion() {
|
||||
if (version < 7)
|
||||
return;
|
||||
|
||||
// Calculate error correction code and pack bits
|
||||
var rem = version; // version is uint6, in the range [7, 40]
|
||||
for (var i = 0; i < 12; i++)
|
||||
rem = (rem << 1) ^ ((rem >>> 11) * 0x1F25);
|
||||
var bits = version << 12 | rem; // uint18
|
||||
if (bits >>> 18 !== 0)
|
||||
throw 'Assertion error';
|
||||
|
||||
// Draw two copies
|
||||
for (var i = 0; i < 18; i++) {
|
||||
var bit = getBit(bits, i);
|
||||
var a = size - 11 + i % 3;
|
||||
var b = Math.floor(i / 3);
|
||||
setFunctionModule(a, b, bit);
|
||||
setFunctionModule(b, a, bit);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draws a 9*9 finder pattern including the border separator,
|
||||
// with the center module at (x, y). Modules can be out of bounds.
|
||||
function drawFinderPattern(x, y) {
|
||||
for (var dy = -4; dy <= 4; dy++) {
|
||||
for (var dx = -4; dx <= 4; dx++) {
|
||||
var dist = Math.max(Math.abs(dx), Math.abs(dy)); // Chebyshev/infinity norm
|
||||
var xx = x + dx, yy = y + dy;
|
||||
if (0 <= xx && xx < size && 0 <= yy && yy < size)
|
||||
setFunctionModule(xx, yy, dist !== 2 && dist !== 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draws a 5*5 alignment pattern, with the center module
|
||||
// at (x, y). All modules must be in bounds.
|
||||
function drawAlignmentPattern(x, y) {
|
||||
for (var dy = -2; dy <= 2; dy++) {
|
||||
for (var dx = -2; dx <= 2; dx++)
|
||||
setFunctionModule(x + dx, y + dy, Math.max(Math.abs(dx), Math.abs(dy)) !== 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Sets the color of a module and marks it as a function module.
|
||||
// Only used by the constructor. Coordinates must be in bounds.
|
||||
function setFunctionModule(x, y, isBlack) {
|
||||
modules[y][x] = isBlack;
|
||||
isFunction[y][x] = true;
|
||||
}
|
||||
|
||||
|
||||
/*---- Private helper methods for constructor: Codewords and masking ----*/
|
||||
|
||||
// Returns a new byte string representing the given data with the appropriate error correction
|
||||
// codewords appended to it, based on this object's version and error correction level.
|
||||
function addEccAndInterleave(data) {
|
||||
if (data.length !== QrCode.getNumDataCodewords(version, errCorLvl))
|
||||
throw 'Invalid argument';
|
||||
|
||||
// Calculate parameter numbers
|
||||
var numBlocks = QrCode.NUM_ERROR_CORRECTION_BLOCKS[errCorLvl.ordinal][version];
|
||||
var blockEccLen = QrCode.ECC_CODEWORDS_PER_BLOCK [errCorLvl.ordinal][version];
|
||||
var rawCodewords = Math.floor(QrCode.getNumRawDataModules(version) / 8);
|
||||
var numShortBlocks = numBlocks - rawCodewords % numBlocks;
|
||||
var shortBlockLen = Math.floor(rawCodewords / numBlocks);
|
||||
|
||||
// Split data into blocks and append ECC to each block
|
||||
var blocks = [];
|
||||
var rs = new ReedSolomonGenerator(blockEccLen);
|
||||
for (var i = 0, k = 0; i < numBlocks; i++) {
|
||||
var dat = data.slice(k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1));
|
||||
k += dat.length;
|
||||
var ecc = rs.getRemainder(dat);
|
||||
if (i < numShortBlocks)
|
||||
dat.push(0);
|
||||
blocks.push(dat.concat(ecc));
|
||||
}
|
||||
|
||||
// Interleave (not concatenate) the bytes from every block into a single sequence
|
||||
var result = [];
|
||||
for (var i = 0; i < blocks[0].length; i++) {
|
||||
for (var j = 0; j < blocks.length; j++) {
|
||||
// Skip the padding byte in short blocks
|
||||
if (i !== shortBlockLen - blockEccLen || j >= numShortBlocks)
|
||||
result.push(blocks[j][i]);
|
||||
}
|
||||
}
|
||||
if (result.length !== rawCodewords)
|
||||
throw 'Assertion error';
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire
|
||||
// data area of this QR Code. Function modules need to be marked off before this is called.
|
||||
function drawCodewords(data) {
|
||||
if (data.length !== Math.floor(QrCode.getNumRawDataModules(version) / 8))
|
||||
throw 'Invalid argument';
|
||||
var i = 0; // Bit index into the data
|
||||
// Do the funny zigzag scan
|
||||
for (var right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair
|
||||
if (right === 6)
|
||||
right = 5;
|
||||
for (var vert = 0; vert < size; vert++) { // Vertical counter
|
||||
for (var j = 0; j < 2; j++) {
|
||||
var x = right - j; // Actual x coordinate
|
||||
var upward = ((right + 1) & 2) === 0;
|
||||
var y = upward ? size - 1 - vert : vert; // Actual y coordinate
|
||||
if (!isFunction[y][x] && i < data.length * 8) {
|
||||
modules[y][x] = getBit(data[i >>> 3], 7 - (i & 7));
|
||||
i++;
|
||||
}
|
||||
// If this QR Code has any remainder bits (0 to 7), they were assigned as
|
||||
// 0/false/white by the constructor and are left unchanged by this method
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i !== data.length * 8)
|
||||
throw 'Assertion error';
|
||||
}
|
||||
|
||||
|
||||
// XORs the codeword modules in this QR Code with the given mask pattern.
|
||||
// The function modules must be marked and the codeword bits must be drawn
|
||||
// before masking. Due to the arithmetic of XOR, calling applyMask() with
|
||||
// the same mask value a second time will undo the mask. A final well-formed
|
||||
// QR Code needs exactly one (not zero, two, etc.) mask applied.
|
||||
function applyMask(mask) {
|
||||
if (mask < 0 || mask > 7)
|
||||
throw 'Mask value out of range';
|
||||
for (var y = 0; y < size; y++) {
|
||||
for (var x = 0; x < size; x++) {
|
||||
var invert;
|
||||
switch (mask) {
|
||||
case 0: invert = (x + y) % 2 === 0; break;
|
||||
case 1: invert = y % 2 === 0; break;
|
||||
case 2: invert = x % 3 === 0; break;
|
||||
case 3: invert = (x + y) % 3 === 0; break;
|
||||
case 4: invert = (Math.floor(x / 3) + Math.floor(y / 2)) % 2 === 0; break;
|
||||
case 5: invert = x * y % 2 + x * y % 3 === 0; break;
|
||||
case 6: invert = (x * y % 2 + x * y % 3) % 2 === 0; break;
|
||||
case 7: invert = ((x + y) % 2 + x * y % 3) % 2 === 0; break;
|
||||
default: throw 'Assertion error';
|
||||
}
|
||||
if (!isFunction[y][x] && invert)
|
||||
modules[y][x] = !modules[y][x];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Calculates and returns the penalty score based on state of this QR Code's current modules.
|
||||
// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.
|
||||
function getPenaltyScore() {
|
||||
var result = 0;
|
||||
|
||||
// Adjacent modules in row having same color, and finder-like patterns
|
||||
for (var y = 0; y < size; y++) {
|
||||
var runHistory = [0,0,0,0,0,0,0];
|
||||
var color = false;
|
||||
var runX = 0;
|
||||
for (var x = 0; x < size; x++) {
|
||||
if (modules[y][x] === color) {
|
||||
runX++;
|
||||
if (runX === 5)
|
||||
result += QrCode.PENALTY_N1;
|
||||
else if (runX > 5)
|
||||
result++;
|
||||
} else {
|
||||
QrCode.addRunToHistory(runX, runHistory);
|
||||
if (!color && QrCode.hasFinderLikePattern(runHistory))
|
||||
result += QrCode.PENALTY_N3;
|
||||
color = modules[y][x];
|
||||
runX = 1;
|
||||
}
|
||||
}
|
||||
QrCode.addRunToHistory(runX, runHistory);
|
||||
if (color)
|
||||
QrCode.addRunToHistory(0, runHistory); // Dummy run of white
|
||||
if (QrCode.hasFinderLikePattern(runHistory))
|
||||
result += QrCode.PENALTY_N3;
|
||||
}
|
||||
// Adjacent modules in column having same color, and finder-like patterns
|
||||
for (var x = 0; x < size; x++) {
|
||||
var runHistory = [0,0,0,0,0,0,0];
|
||||
var color = false;
|
||||
var runY = 0;
|
||||
for (var y = 0; y < size; y++) {
|
||||
if (modules[y][x] === color) {
|
||||
runY++;
|
||||
if (runY === 5)
|
||||
result += QrCode.PENALTY_N1;
|
||||
else if (runY > 5)
|
||||
result++;
|
||||
} else {
|
||||
QrCode.addRunToHistory(runY, runHistory);
|
||||
if (!color && QrCode.hasFinderLikePattern(runHistory))
|
||||
result += QrCode.PENALTY_N3;
|
||||
color = modules[y][x];
|
||||
runY = 1;
|
||||
}
|
||||
}
|
||||
QrCode.addRunToHistory(runY, runHistory);
|
||||
if (color)
|
||||
QrCode.addRunToHistory(0, runHistory); // Dummy run of white
|
||||
if (QrCode.hasFinderLikePattern(runHistory))
|
||||
result += QrCode.PENALTY_N3;
|
||||
}
|
||||
|
||||
// 2*2 blocks of modules having same color
|
||||
for (var y = 0; y < size - 1; y++) {
|
||||
for (var x = 0; x < size - 1; x++) {
|
||||
var color = modules[y][x];
|
||||
if (color === modules[y][x + 1] && color === modules[y + 1][x] && color === modules[y + 1][x + 1])
|
||||
result += QrCode.PENALTY_N2;
|
||||
}
|
||||
}
|
||||
|
||||
// Balance of black and white modules
|
||||
var black = 0;
|
||||
modules.forEach(function(row) {
|
||||
row.forEach(function(color) {
|
||||
if (color)
|
||||
black++;
|
||||
});
|
||||
});
|
||||
var total = size * size; // Note that size is odd, so black/total !== 1/2
|
||||
// Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)%
|
||||
var k = Math.ceil(Math.abs(black * 20 - total * 10) / total) - 1;
|
||||
result += k * QrCode.PENALTY_N4;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Returns an ascending list of positions of alignment patterns for this version number.
|
||||
// Each position is in the range [0,177), and are used on both the x and y axes.
|
||||
// This could be implemented as lookup table of 40 variable-length lists of integers.
|
||||
function getAlignmentPatternPositions() {
|
||||
if (version === 1)
|
||||
return [];
|
||||
else {
|
||||
var numAlign = Math.floor(version / 7) + 2;
|
||||
var step = (version === 32) ? 26 :
|
||||
Math.ceil((size - 13) / (numAlign*2 - 2)) * 2;
|
||||
var result = [6];
|
||||
for (var pos = size - 7; result.length < numAlign; pos -= step)
|
||||
result.splice(1, 0, pos);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Returns true iff the i'th bit of x is set to 1.
|
||||
function getBit(x, i) {
|
||||
return ((x >>> i) & 1) !== 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*---- Static factory functions (high level) for QrCode ----*/
|
||||
|
||||
/*
|
||||
* Returns a QR Code representing the given Unicode text string at the given error correction level.
|
||||
* As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer
|
||||
* Unicode code points (not UTF-16 code units) if the low error correction level is used. The smallest possible
|
||||
* QR Code version is automatically chosen for the output. The ECC level of the result may be higher than the
|
||||
* ecl argument if it can be done without increasing the version.
|
||||
*/
|
||||
this.QrCode.encodeText = function(text, ecl) {
|
||||
var segs = qrcodegen.QrSegment.makeSegments(text);
|
||||
return this.encodeSegments(segs, ecl);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Returns a QR Code representing the given binary data at the given error correction level.
|
||||
* This function always encodes using the binary segment mode, not any text mode. The maximum number of
|
||||
* bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output.
|
||||
* The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version.
|
||||
*/
|
||||
this.QrCode.encodeBinary = function(data, ecl) {
|
||||
var seg = qrcodegen.QrSegment.makeBytes(data);
|
||||
return this.encodeSegments([seg], ecl);
|
||||
};
|
||||
|
||||
|
||||
/*---- Static factory functions (mid level) for QrCode ----*/
|
||||
|
||||
/*
|
||||
* Returns a QR Code representing the given segments with the given encoding parameters.
|
||||
* The smallest possible QR Code version within the given range is automatically
|
||||
* chosen for the output. Iff boostEcl is true, then the ECC level of the result
|
||||
* may be higher than the ecl argument if it can be done without increasing the
|
||||
* version. The mask number is either between 0 to 7 (inclusive) to force that
|
||||
* mask, or -1 to automatically choose an appropriate mask (which may be slow).
|
||||
* This function allows the user to create a custom sequence of segments that switches
|
||||
* between modes (such as alphanumeric and byte) to encode text in less space.
|
||||
* This is a mid-level API; the high-level API is encodeText() and encodeBinary().
|
||||
*/
|
||||
this.QrCode.encodeSegments = function(segs, ecl, minVersion, maxVersion, mask, boostEcl) {
|
||||
if (minVersion === undefined) minVersion = MIN_VERSION;
|
||||
if (maxVersion === undefined) maxVersion = MAX_VERSION;
|
||||
if (mask === undefined) mask = -1;
|
||||
if (boostEcl === undefined) boostEcl = true;
|
||||
if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7)
|
||||
throw 'Invalid value';
|
||||
|
||||
// Find the minimal version number to use
|
||||
var version, dataUsedBits;
|
||||
for (version = minVersion; ; version++) {
|
||||
var dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8; // Number of data bits available
|
||||
dataUsedBits = qrcodegen.QrSegment.getTotalBits(segs, version);
|
||||
if (dataUsedBits <= dataCapacityBits)
|
||||
break; // This version number is found to be suitable
|
||||
if (version >= maxVersion) // All versions in the range could not fit the given data
|
||||
throw 'Data too long';
|
||||
}
|
||||
|
||||
// Increase the error correction level while the data still fits in the current version number
|
||||
[this.Ecc.MEDIUM, this.Ecc.QUARTILE, this.Ecc.HIGH].forEach(function(newEcl) { // From low to high
|
||||
if (boostEcl && dataUsedBits <= QrCode.getNumDataCodewords(version, newEcl) * 8)
|
||||
ecl = newEcl;
|
||||
});
|
||||
|
||||
// Concatenate all segments to create the data bit string
|
||||
var bb = new BitBuffer();
|
||||
segs.forEach(function(seg) {
|
||||
bb.appendBits(seg.mode.modeBits, 4);
|
||||
bb.appendBits(seg.numChars, seg.mode.numCharCountBits(version));
|
||||
seg.getData().forEach(function(bit) {
|
||||
bb.push(bit);
|
||||
});
|
||||
});
|
||||
if (bb.length !== dataUsedBits)
|
||||
throw 'Assertion error';
|
||||
|
||||
// Add terminator and pad up to a byte if applicable
|
||||
var dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8;
|
||||
if (bb.length > dataCapacityBits)
|
||||
throw 'Assertion error';
|
||||
bb.appendBits(0, Math.min(4, dataCapacityBits - bb.length));
|
||||
bb.appendBits(0, (8 - bb.length % 8) % 8);
|
||||
if (bb.length % 8 !== 0)
|
||||
throw 'Assertion error';
|
||||
|
||||
// Pad with alternating bytes until data capacity is reached
|
||||
for (var padByte = 0xEC; bb.length < dataCapacityBits; padByte ^= 0xEC ^ 0x11)
|
||||
bb.appendBits(padByte, 8);
|
||||
|
||||
// Pack bits into bytes in big endian
|
||||
var dataCodewords = [];
|
||||
while (dataCodewords.length * 8 < bb.length)
|
||||
dataCodewords.push(0);
|
||||
bb.forEach(function(bit, i) {
|
||||
dataCodewords[i >>> 3] |= bit << (7 - (i & 7));
|
||||
});
|
||||
|
||||
// Create the QR Code object
|
||||
return new this(version, ecl, dataCodewords, mask);
|
||||
};
|
||||
|
||||
|
||||
/*---- Private static helper functions for QrCode ----*/
|
||||
|
||||
var QrCode = {}; // Private object to assign properties to. Not the same object as 'this.QrCode'.
|
||||
|
||||
|
||||
// Returns the number of data bits that can be stored in a QR Code of the given version number, after
|
||||
// all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8.
|
||||
// The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table.
|
||||
QrCode.getNumRawDataModules = function(ver) {
|
||||
if (ver < MIN_VERSION || ver > MAX_VERSION)
|
||||
throw 'Version number out of range';
|
||||
var result = (16 * ver + 128) * ver + 64;
|
||||
if (ver >= 2) {
|
||||
var numAlign = Math.floor(ver / 7) + 2;
|
||||
result -= (25 * numAlign - 10) * numAlign - 55;
|
||||
if (ver >= 7)
|
||||
result -= 36;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// Returns the number of 8-bit data (i.e. not error correction) codewords contained in any
|
||||
// QR Code of the given version number and error correction level, with remainder bits discarded.
|
||||
// This stateless pure function could be implemented as a (40*4)-cell lookup table.
|
||||
QrCode.getNumDataCodewords = function(ver, ecl) {
|
||||
return Math.floor(QrCode.getNumRawDataModules(ver) / 8) -
|
||||
QrCode.ECC_CODEWORDS_PER_BLOCK [ecl.ordinal][ver] *
|
||||
QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver];
|
||||
};
|
||||
|
||||
|
||||
// Inserts the given value to the front of the given array, which shifts over the
|
||||
// existing values and deletes the last value. A helper function for getPenaltyScore().
|
||||
QrCode.addRunToHistory = function(run, history) {
|
||||
history.pop();
|
||||
history.unshift(run);
|
||||
};
|
||||
|
||||
|
||||
// Tests whether the given run history has the pattern of ratio 1:1:3:1:1 in the middle, and
|
||||
// surrounded by at least 4 on either or both ends. A helper function for getPenaltyScore().
|
||||
// Must only be called immediately after a run of white modules has ended.
|
||||
QrCode.hasFinderLikePattern = function(runHistory) {
|
||||
var n = runHistory[1];
|
||||
return n > 0 && runHistory[2] === n && runHistory[4] === n && runHistory[5] === n
|
||||
&& runHistory[3] === n * 3 && Math.max(runHistory[0], runHistory[6]) >= n * 4;
|
||||
};
|
||||
|
||||
|
||||
/*---- Constants and tables for QrCode ----*/
|
||||
|
||||
var MIN_VERSION = 1; // The minimum version number supported in the QR Code Model 2 standard
|
||||
var MAX_VERSION = 40; // The maximum version number supported in the QR Code Model 2 standard
|
||||
Object.defineProperty(this.QrCode, 'MIN_VERSION', {value:MIN_VERSION});
|
||||
Object.defineProperty(this.QrCode, 'MAX_VERSION', {value:MAX_VERSION});
|
||||
|
||||
// For use in getPenaltyScore(), when evaluating which mask is best.
|
||||
QrCode.PENALTY_N1 = 3;
|
||||
QrCode.PENALTY_N2 = 3;
|
||||
QrCode.PENALTY_N3 = 40;
|
||||
QrCode.PENALTY_N4 = 10;
|
||||
|
||||
QrCode.ECC_CODEWORDS_PER_BLOCK = [
|
||||
// Version: (note that index 0 is for padding, and is set to an illegal value)
|
||||
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
|
||||
[null, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], // Low
|
||||
[null, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28], // Medium
|
||||
[null, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], // Quartile
|
||||
[null, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], // High
|
||||
];
|
||||
|
||||
QrCode.NUM_ERROR_CORRECTION_BLOCKS = [
|
||||
// Version: (note that index 0 is for padding, and is set to an illegal value)
|
||||
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
|
||||
[null, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25], // Low
|
||||
[null, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49], // Medium
|
||||
[null, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68], // Quartile
|
||||
[null, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81], // High
|
||||
];
|
||||
|
||||
|
||||
/*---- Public helper enumeration ----*/
|
||||
|
||||
/*
|
||||
* The error correction level in a QR Code symbol. Immutable.
|
||||
*/
|
||||
this.QrCode.Ecc = {
|
||||
LOW : new Ecc(0, 1), // The QR Code can tolerate about 7% erroneous codewords
|
||||
MEDIUM : new Ecc(1, 0), // The QR Code can tolerate about 15% erroneous codewords
|
||||
QUARTILE: new Ecc(2, 3), // The QR Code can tolerate about 25% erroneous codewords
|
||||
HIGH : new Ecc(3, 2), // The QR Code can tolerate about 30% erroneous codewords
|
||||
};
|
||||
|
||||
|
||||
// Private constructor.
|
||||
function Ecc(ord, fb) {
|
||||
// (Public) In the range 0 to 3 (unsigned 2-bit integer)
|
||||
Object.defineProperty(this, 'ordinal', {value:ord});
|
||||
|
||||
// (Package-private) In the range 0 to 3 (unsigned 2-bit integer)
|
||||
Object.defineProperty(this, 'formatBits', {value:fb});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*---- Data segment class ----*/
|
||||
|
||||
/*
|
||||
* A segment of character/binary/control data in a QR Code symbol.
|
||||
* Instances of this class are immutable.
|
||||
* The mid-level way to create a segment is to take the payload data
|
||||
* and call a static factory function such as QrSegment.makeNumeric().
|
||||
* The low-level way to create a segment is to custom-make the bit buffer
|
||||
* and call the QrSegment() constructor with appropriate values.
|
||||
* This segment class imposes no length restrictions, but QR Codes have restrictions.
|
||||
* Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.
|
||||
* Any segment longer than this is meaningless for the purpose of generating QR Codes.
|
||||
* This constructor creates a QR Code segment with the given attributes and data.
|
||||
* The character count (numChars) must agree with the mode and the bit buffer length,
|
||||
* but the constraint isn't checked. The given bit buffer is cloned and stored.
|
||||
*/
|
||||
this.QrSegment = function(mode, numChars, bitData) {
|
||||
/*---- Constructor (low level) ----*/
|
||||
if (numChars < 0 || !(mode instanceof Mode))
|
||||
throw 'Invalid argument';
|
||||
|
||||
// The data bits of this segment. Accessed through getData().
|
||||
bitData = bitData.slice(); // Make defensive copy
|
||||
|
||||
// The mode indicator of this segment.
|
||||
Object.defineProperty(this, 'mode', {value:mode});
|
||||
|
||||
// The length of this segment's unencoded data. Measured in characters for
|
||||
// numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.
|
||||
// Always zero or positive. Not the same as the data's bit length.
|
||||
Object.defineProperty(this, 'numChars', {value:numChars});
|
||||
|
||||
// Returns a new copy of the data bits of this segment.
|
||||
this.getData = function() {
|
||||
return bitData.slice(); // Make defensive copy
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/*---- Static factory functions (mid level) for QrSegment ----*/
|
||||
|
||||
/*
|
||||
* Returns a segment representing the given binary data encoded in
|
||||
* byte mode. All input byte arrays are acceptable. Any text string
|
||||
* can be converted to UTF-8 bytes and encoded as a byte mode segment.
|
||||
*/
|
||||
this.QrSegment.makeBytes = function(data) {
|
||||
var bb = new BitBuffer();
|
||||
data.forEach(function(b) {
|
||||
bb.appendBits(b, 8);
|
||||
});
|
||||
return new this(this.Mode.BYTE, data.length, bb);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing the given string of decimal digits encoded in numeric mode.
|
||||
*/
|
||||
this.QrSegment.makeNumeric = function(digits) {
|
||||
if (!this.NUMERIC_REGEX.test(digits))
|
||||
throw 'String contains non-numeric characters';
|
||||
var bb = new BitBuffer();
|
||||
for (var i = 0; i < digits.length; ) { // Consume up to 3 digits per iteration
|
||||
var n = Math.min(digits.length - i, 3);
|
||||
bb.appendBits(parseInt(digits.substring(i, i + n), 10), n * 3 + 1);
|
||||
i += n;
|
||||
}
|
||||
return new this(this.Mode.NUMERIC, digits.length, bb);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing the given text string encoded in alphanumeric mode.
|
||||
* The characters allowed are: 0 to 9, A to Z (uppercase only), space,
|
||||
* dollar, percent, asterisk, plus, hyphen, period, slash, colon.
|
||||
*/
|
||||
this.QrSegment.makeAlphanumeric = function(text) {
|
||||
if (!this.ALPHANUMERIC_REGEX.test(text))
|
||||
throw 'String contains unencodable characters in alphanumeric mode';
|
||||
var bb = new BitBuffer();
|
||||
var i;
|
||||
for (i = 0; i + 2 <= text.length; i += 2) { // Process groups of 2
|
||||
var temp = QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)) * 45;
|
||||
temp += QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i + 1));
|
||||
bb.appendBits(temp, 11);
|
||||
}
|
||||
if (i < text.length) // 1 character remaining
|
||||
bb.appendBits(QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)), 6);
|
||||
return new this(this.Mode.ALPHANUMERIC, text.length, bb);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Returns a new mutable list of zero or more segments to represent the given Unicode text string.
|
||||
* The result may use various segment modes and switch modes to optimize the length of the bit stream.
|
||||
*/
|
||||
this.QrSegment.makeSegments = function(text) {
|
||||
// Select the most efficient segment encoding automatically
|
||||
if (text === '')
|
||||
return [];
|
||||
else if (this.NUMERIC_REGEX.test(text))
|
||||
return [this.makeNumeric(text)];
|
||||
else if (this.ALPHANUMERIC_REGEX.test(text))
|
||||
return [this.makeAlphanumeric(text)];
|
||||
else
|
||||
return [this.makeBytes(toUtf8ByteArray(text))];
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing an Extended Channel Interpretation
|
||||
* (ECI) designator with the given assignment value.
|
||||
*/
|
||||
this.QrSegment.makeEci = function(assignVal) {
|
||||
var bb = new BitBuffer();
|
||||
if (assignVal < 0)
|
||||
throw 'ECI assignment value out of range';
|
||||
else if (assignVal < (1 << 7))
|
||||
bb.appendBits(assignVal, 8);
|
||||
else if (assignVal < (1 << 14)) {
|
||||
bb.appendBits(2, 2);
|
||||
bb.appendBits(assignVal, 14);
|
||||
} else if (assignVal < 1000000) {
|
||||
bb.appendBits(6, 3);
|
||||
bb.appendBits(assignVal, 21);
|
||||
} else
|
||||
throw 'ECI assignment value out of range';
|
||||
return new this(this.Mode.ECI, 0, bb);
|
||||
};
|
||||
|
||||
|
||||
// (Package-private) Calculates and returns the number of bits needed to encode the given segments at the
|
||||
// given version. The result is infinity if a segment has too many characters to fit its length field.
|
||||
this.QrSegment.getTotalBits = function(segs, version) {
|
||||
var result = 0;
|
||||
for (var i = 0; i < segs.length; i++) {
|
||||
var seg = segs[i];
|
||||
var ccbits = seg.mode.numCharCountBits(version);
|
||||
if (seg.numChars >= (1 << ccbits))
|
||||
return Infinity; // The segment's length doesn't fit the field's bit width
|
||||
result += 4 + ccbits + seg.getData().length;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/*---- Constants for QrSegment ----*/
|
||||
|
||||
var QrSegment = {}; // Private object to assign properties to. Not the same object as 'this.QrSegment'.
|
||||
|
||||
// (Public) Describes precisely all strings that are encodable in numeric mode.
|
||||
// To test whether a string s is encodable: var ok = NUMERIC_REGEX.test(s);
|
||||
// A string is encodable iff each character is in the range 0 to 9.
|
||||
this.QrSegment.NUMERIC_REGEX = /^[0-9]*$/;
|
||||
|
||||
// (Public) Describes precisely all strings that are encodable in alphanumeric mode.
|
||||
// To test whether a string s is encodable: var ok = ALPHANUMERIC_REGEX.test(s);
|
||||
// A string is encodable iff each character is in the following set: 0 to 9, A to Z
|
||||
// (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.
|
||||
this.QrSegment.ALPHANUMERIC_REGEX = /^[A-Z0-9 $%*+.\/:-]*$/;
|
||||
|
||||
// (Private) The set of all legal characters in alphanumeric mode,
|
||||
// where each character value maps to the index in the string.
|
||||
QrSegment.ALPHANUMERIC_CHARSET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
|
||||
|
||||
|
||||
/*---- Public helper enumeration ----*/
|
||||
|
||||
/*
|
||||
* Describes how a segment's data bits are interpreted. Immutable.
|
||||
*/
|
||||
this.QrSegment.Mode = { // Constants
|
||||
NUMERIC : new Mode(0x1, [10, 12, 14]),
|
||||
ALPHANUMERIC: new Mode(0x2, [ 9, 11, 13]),
|
||||
BYTE : new Mode(0x4, [ 8, 16, 16]),
|
||||
KANJI : new Mode(0x8, [ 8, 10, 12]),
|
||||
ECI : new Mode(0x7, [ 0, 0, 0]),
|
||||
};
|
||||
|
||||
|
||||
// Private constructor.
|
||||
function Mode(mode, ccbits) {
|
||||
// (Package-private) The mode indicator bits, which is a uint4 value (range 0 to 15).
|
||||
Object.defineProperty(this, 'modeBits', {value:mode});
|
||||
|
||||
// (Package-private) Returns the bit width of the character count field for a segment in
|
||||
// this mode in a QR Code at the given version number. The result is in the range [0, 16].
|
||||
this.numCharCountBits = function(ver) {
|
||||
return ccbits[Math.floor((ver + 7) / 17)];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*---- Private helper functions and classes ----*/
|
||||
|
||||
// Returns a new array of bytes representing the given string encoded in UTF-8.
|
||||
function toUtf8ByteArray(str) {
|
||||
str = encodeURI(str);
|
||||
var result = [];
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
if (str.charAt(i) !== '%')
|
||||
result.push(str.charCodeAt(i));
|
||||
else {
|
||||
result.push(parseInt(str.substring(i + 1, i + 3), 16));
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* A private helper class that computes the Reed-Solomon error correction codewords for a sequence of
|
||||
* data codewords at a given degree. Objects are immutable, and the state only depends on the degree.
|
||||
* This class exists because each data block in a QR Code shares the same the divisor polynomial.
|
||||
* This constructor creates a Reed-Solomon ECC generator for the given degree. This could be implemented
|
||||
* as a lookup table over all possible parameter values, instead of as an algorithm.
|
||||
*/
|
||||
function ReedSolomonGenerator(degree) {
|
||||
if (degree < 1 || degree > 255)
|
||||
throw 'Degree out of range';
|
||||
|
||||
// Coefficients of the divisor polynomial, stored from highest to lowest power, excluding the leading term which
|
||||
// is always 1. For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}.
|
||||
var coefficients = [];
|
||||
|
||||
// Start with the monomial x^0
|
||||
for (var i = 0; i < degree - 1; i++)
|
||||
coefficients.push(0);
|
||||
coefficients.push(1);
|
||||
|
||||
// Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),
|
||||
// drop the highest term, and store the rest of the coefficients in order of descending powers.
|
||||
// Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D).
|
||||
var root = 1;
|
||||
for (var i = 0; i < degree; i++) {
|
||||
// Multiply the current product by (x - r^i)
|
||||
for (var j = 0; j < coefficients.length; j++) {
|
||||
coefficients[j] = ReedSolomonGenerator.multiply(coefficients[j], root);
|
||||
if (j + 1 < coefficients.length)
|
||||
coefficients[j] ^= coefficients[j + 1];
|
||||
}
|
||||
root = ReedSolomonGenerator.multiply(root, 0x02);
|
||||
}
|
||||
|
||||
// Computes and returns the Reed-Solomon error correction codewords for the given
|
||||
// sequence of data codewords. The returned object is always a new byte array.
|
||||
// This method does not alter this object's state (because it is immutable).
|
||||
this.getRemainder = function(data) {
|
||||
// Compute the remainder by performing polynomial division
|
||||
var result = coefficients.map(function() { return 0; });
|
||||
data.forEach(function(b) {
|
||||
var factor = b ^ result.shift();
|
||||
result.push(0);
|
||||
coefficients.forEach(function(coef, i) {
|
||||
result[i] ^= ReedSolomonGenerator.multiply(coef, factor);
|
||||
});
|
||||
});
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
// This static function returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and
|
||||
// result are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8.
|
||||
ReedSolomonGenerator.multiply = function(x, y) {
|
||||
if (x >>> 8 !== 0 || y >>> 8 !== 0)
|
||||
throw 'Byte out of range';
|
||||
// Russian peasant multiplication
|
||||
var z = 0;
|
||||
for (var i = 7; i >= 0; i--) {
|
||||
z = (z << 1) ^ ((z >>> 7) * 0x11D);
|
||||
z ^= ((y >>> i) & 1) * x;
|
||||
}
|
||||
if (z >>> 8 !== 0)
|
||||
throw 'Assertion error';
|
||||
return z;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* A private helper class that represents an appendable sequence of bits (0s and 1s).
|
||||
* Mainly used by QrSegment. This constructor creates an empty bit buffer (length 0).
|
||||
*/
|
||||
function BitBuffer() {
|
||||
Array.call(this);
|
||||
|
||||
// Appends the given number of low-order bits of the given value
|
||||
// to this buffer. Requires 0 <= len <= 31 and 0 <= val < 2^len.
|
||||
this.appendBits = function(val, len) {
|
||||
if (len < 0 || len > 31 || val >>> len !== 0)
|
||||
throw 'Value out of range';
|
||||
for (var i = len - 1; i >= 0; i--) // Append bit by bit
|
||||
this.push((val >>> i) & 1);
|
||||
};
|
||||
}
|
||||
|
||||
BitBuffer.prototype = Object.create(Array.prototype);
|
||||
BitBuffer.prototype.constructor = BitBuffer;
|
||||
|
||||
};
|
187
load/session.js
Normal file
187
load/session.js
Normal file
@ -0,0 +1,187 @@
|
||||
/**
|
||||
* This handles session specific aspects of each frame type, eg: sending to the baseline, clearing, accepting
|
||||
* input, moving around the frame (and windows within one if any)
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function Session() {
|
||||
'use strict';
|
||||
|
||||
// Our page object
|
||||
this.page = undefined;
|
||||
this.previous = undefined;
|
||||
|
||||
/* Frame type settings */
|
||||
this.settings = {};
|
||||
|
||||
/**
|
||||
* Enable pulling out submitted value by its name
|
||||
*
|
||||
* @param key
|
||||
* @returns {null|string|*}
|
||||
*/
|
||||
this.fieldValue = function(key) {
|
||||
for each (var k in this.page.input_fields) {
|
||||
if (k.name === key)
|
||||
return k.value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
this.get = function(page) {
|
||||
if (!(page instanceof PageObject))
|
||||
throw new Error('page must be a PageObject');
|
||||
|
||||
this.baselineSend('LOADING');
|
||||
// Just in case our new page doesnt exist
|
||||
this.previous = this.page;
|
||||
|
||||
this.page = new Page();
|
||||
var result = this.page.get(page);
|
||||
|
||||
// If our new page doesnt exist, reset our page back to the current
|
||||
if (! result)
|
||||
this.page = this.previous;
|
||||
|
||||
this.previous = undefined;
|
||||
this.baselineClear();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a message frame
|
||||
*
|
||||
* @param pagenum
|
||||
*/
|
||||
this.loadMessage = function(pagenum,ext) {
|
||||
log(LOG_ERROR,'Loading Message :['+pagenum+']');
|
||||
|
||||
// Load our message
|
||||
var ma = new MsgAreas()
|
||||
var area = ma.getArea(pagenum);
|
||||
var msg = ma.getMessage(pagenum);
|
||||
var msg_header;
|
||||
|
||||
if (! msg)
|
||||
return false;
|
||||
|
||||
// @todo Search 1zzzzEE..., 1zzzz...
|
||||
if (! this.get(new PageObject(MAIL_TEMPLATE_FRAME),ext)) {
|
||||
this.page = new Page();
|
||||
|
||||
log(LOG_ERROR,'Echomail template missing :['+JSON.stringify(MAIL_TEMPLATE_FRAME)+'] ?');
|
||||
|
||||
msg_header = 'TO: '+msg.to.substr(0,72)+"\r\n";
|
||||
msg_header += 'FROM: '+msg.from.substr(0,72)+"\r\n";
|
||||
msg_header += 'DATE: '+msg.date.substr(0,72)+"\r\n";
|
||||
msg_header += 'SUBJECT: '+msg.subject.substr(0,72)+"\r\n";
|
||||
|
||||
} else {
|
||||
// @todo change this to use atcode()
|
||||
msg_header = this.page.raw.replace(/@(.*)@/g,
|
||||
function (str, code, offset, s) {
|
||||
var length = code.split(':')[1];
|
||||
switch(code.split(':')[0]) {
|
||||
case 'DATE': return msg.date.substr(0,length);
|
||||
case 'FROM': return msg.from.substr(0,length);
|
||||
case 'SUBJECT': return msg.subject.substr(0,length);
|
||||
case 'TO': return msg.to.substr(0,length);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this.page.name = new PageObject(pagenum,'a');
|
||||
this.page.owner = 1;
|
||||
// @todo Keys should map to next/previous/send, etc as indicated in the template frame.
|
||||
this.page.key = [this.page.name.frame.substr(0,7)+'1',null,null,null,null,null,null,null,null,null];
|
||||
// @todo validate that FRAME_TYPE_MESSAGE is a message template
|
||||
this.page.type = FRAME_TYPE_MESSAGE;
|
||||
// @todo Take the cost from the template
|
||||
this.page.cost = 5;
|
||||
// @todo Take the key values from the template
|
||||
this.page.__properties__.isAccessible = true;
|
||||
this.page.__properties__.isPublic = true;
|
||||
|
||||
this.page.preload(msg_header+msg.content,'txt');
|
||||
|
||||
// Update the user's pointers
|
||||
var stats = ma.getUserStats(this.page.name.frame);
|
||||
|
||||
// if this message is to the user, and the msg number > scan_ptr and it is the next message on the user's new mail list
|
||||
var newmsgs = area.newMsgsToMe();
|
||||
var next;
|
||||
log(LOG_DEBUG,'User has: '+newmsgs.length-1+' msgs to read to ME');
|
||||
if (newmsgs.length) {
|
||||
next = newmsgs[1];
|
||||
log(LOG_DEBUG,'- NEXT is: '+next.tags+', this is: '+msg.tags);
|
||||
|
||||
if (next && (next.tags === msg.tags)) {
|
||||
log(LOG_DEBUG,'- Updating scan_ptr to: '+next.number);
|
||||
stats.scan_ptr = next.number;
|
||||
}
|
||||
|
||||
// Last message
|
||||
next = newmsgs[0];
|
||||
if (next !== undefined) {
|
||||
log(LOG_DEBUG,'- LAST TO ME is: '+next.tags);
|
||||
this.page.key[1] = area.page(next.tags);
|
||||
}
|
||||
|
||||
// Next new message
|
||||
next = newmsgs[2];
|
||||
if (next !== undefined) {
|
||||
log(LOG_DEBUG,'- NEXT TO ME is: '+next.tags);
|
||||
this.page.key[2] = area.page(next.tags);
|
||||
}
|
||||
}
|
||||
|
||||
// if this message is the next message, update last_read
|
||||
newmsgs = area.newMsgs();
|
||||
log(LOG_DEBUG,'User has: '+newmsgs.length+' msgs to read');
|
||||
if (newmsgs.length) {
|
||||
next = newmsgs[0];
|
||||
log(LOG_DEBUG,'- NEXT is: '+next.tags+', this is: '+msg.tags);
|
||||
|
||||
if (next.tags === msg.tags) {
|
||||
log(LOG_DEBUG,'- Updating last_read to: '+next.number);
|
||||
stats.last_read = next.number;
|
||||
}
|
||||
}
|
||||
|
||||
// Previous Message
|
||||
x = area.MessagePrev(this.page.name.frame);
|
||||
if (x)
|
||||
this.page.key[4] = area.page(x.tags);
|
||||
|
||||
// Next Message
|
||||
x = area.MessageNext(this.page.name.frame);
|
||||
if (x)
|
||||
this.page.key[6] = area.page(x.tags);
|
||||
|
||||
log(LOG_DEBUG,'Built frame: ['+this.page.name.frame+']['+this.page.name.index+'] ('+this.page.name.toString()+')');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Render the page
|
||||
this.render = function() {
|
||||
this.gotoxy(0,0);
|
||||
//console.clear(null,false);
|
||||
write(so.page.display().join(''));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a system message for a index
|
||||
*
|
||||
* @param index
|
||||
* @returns {string|*}
|
||||
* @see SessionProtocol()
|
||||
*/
|
||||
Session.prototype.getMessage = function(index) {
|
||||
eval('var msg = this.settings.'+index);
|
||||
return msg;
|
||||
}
|
596
load/session/ansitex.js
Normal file
596
load/session/ansitex.js
Normal file
@ -0,0 +1,596 @@
|
||||
const SESSION_ANSITEX = (1<<1);
|
||||
var CONTENT_EXT = 'ans';
|
||||
|
||||
var FRAME_WIDTH = 80;
|
||||
var FRAME_HEIGHT = 22;
|
||||
var FRAME_PROVIDER_LENGTH = 55;
|
||||
var FRAME_PAGE_LENGTH = 13;
|
||||
var FRAME_COST_LENGTH = 10;
|
||||
var FRAME_ATTR_LENGTH = 0; // Space that an attribute takes
|
||||
|
||||
/**
|
||||
* This function converts ANSI text into an array of attributes
|
||||
*
|
||||
* We include the attribute for every character, so that if a window is placed on top of this window, the edges
|
||||
* render correctly.
|
||||
*
|
||||
* @param contents - Our ANSI content to convert
|
||||
* @param width - The width before wrapping to the next line
|
||||
* @param yoffset - fields offset as discovered
|
||||
* @param xoffset - fields offset as discovered
|
||||
* @param debug - Enable debug mode
|
||||
*/
|
||||
function rawtoattrs(contents,width,yoffset,xoffset,debug) {
|
||||
if (debug)
|
||||
writeln('DEBUG active: '+debug);
|
||||
|
||||
lines = (''+contents).split(/\r\n/);
|
||||
|
||||
var i = 0;
|
||||
var bg = BG_BLACK;
|
||||
var fg = LIGHTGRAY;
|
||||
var attr = fg + bg + i;
|
||||
|
||||
var y = 0;
|
||||
// Saving cursor positions
|
||||
var saved = {};
|
||||
|
||||
var frame = {
|
||||
content: [],
|
||||
dynamic_fields: [],
|
||||
input_fields: [],
|
||||
};
|
||||
|
||||
// @todo temp hack, rework ansi variable - perhaps have a function that converts an attribute back into an ANSI sequence
|
||||
var ansi = { i: 0, f: 37, b: 40 };
|
||||
var line;
|
||||
var x;
|
||||
|
||||
while (lines.length > 0) {
|
||||
x = 0;
|
||||
line = lines.shift();
|
||||
|
||||
if ((debug !== undefined) && (y > debug)) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
log(LOG_DEBUG,'y:'+y);
|
||||
log(LOG_DEBUG,'line:'+line);
|
||||
}
|
||||
|
||||
while (line.length > 0) {
|
||||
/* parse an attribute sequence*/
|
||||
var m = line.match(/^\x1b\[((\d+)(;?(\d+))*)+m/);
|
||||
if (m !== null) {
|
||||
line = line.substr(m.shift().length);
|
||||
m = m.shift().split(';').sort(function(a,b) { return Number(a) < Number(b) ? -1 : 1; });
|
||||
|
||||
if ((debug !== undefined) && debug === y) {
|
||||
//writeln();
|
||||
log(LOG_DEBUG,'m:'+JSON.stringify(m));
|
||||
}
|
||||
|
||||
// Reset
|
||||
if (Number(m[0]) === 0) {
|
||||
bg = BG_BLACK;
|
||||
fg = LIGHTGRAY;
|
||||
i = 0;
|
||||
ansi = { i:0, b:37, f:40};
|
||||
m.shift();
|
||||
|
||||
if (debug)
|
||||
log(LOG_DEBUG,' - RESET');
|
||||
}
|
||||
|
||||
// High Intensity
|
||||
if (Number(m[0]) === 1) {
|
||||
i += (((i === 0) || (i === BLINK)) ? HIGH : 0);
|
||||
m.shift();
|
||||
ansi.i = 1;
|
||||
|
||||
if (debug && (debug === y))
|
||||
writeln('fg:'+fg+', bg:'+bg+' i:'+i);
|
||||
}
|
||||
|
||||
// Blink
|
||||
if (Number(m[0]) === 5) {
|
||||
i += (((i === 0) || (i === HIGH)) ? BLINK : 0);
|
||||
m.shift();
|
||||
}
|
||||
|
||||
// Foreground
|
||||
if ((Number(m[0]) >= 30) && (Number(m[0]) <= 37)) {
|
||||
ansi.f = Number(m[0]);
|
||||
|
||||
switch(Number(m.shift())) {
|
||||
case 30:
|
||||
fg = BLACK;
|
||||
break;
|
||||
|
||||
case 31:
|
||||
fg = RED;
|
||||
break;
|
||||
|
||||
case 32:
|
||||
fg = GREEN;
|
||||
break;
|
||||
|
||||
case 33:
|
||||
fg = BROWN;
|
||||
break;
|
||||
|
||||
case 34:
|
||||
fg = BLUE;
|
||||
break;
|
||||
|
||||
case 35:
|
||||
fg = MAGENTA;
|
||||
break;
|
||||
|
||||
case 36:
|
||||
fg = CYAN;
|
||||
break;
|
||||
|
||||
case 37:
|
||||
fg = LIGHTGRAY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Background
|
||||
if ((Number(m[0]) >= 40) && (Number(m[0]) <= 47)) {
|
||||
ansi.b = Number(m[0]);
|
||||
|
||||
switch (Number(m.shift())) {
|
||||
case 40:
|
||||
bg = BG_BLACK;
|
||||
break;
|
||||
|
||||
case 41:
|
||||
bg = BG_RED;
|
||||
break;
|
||||
|
||||
case 42:
|
||||
bg = BG_GREEN;
|
||||
break;
|
||||
|
||||
case 43:
|
||||
bg = BG_BROWN;
|
||||
break;
|
||||
|
||||
case 44:
|
||||
bg = BG_BLUE;
|
||||
break;
|
||||
|
||||
case 45:
|
||||
bg = BG_MAGENTA;
|
||||
break;
|
||||
|
||||
case 46:
|
||||
bg = BG_CYAN;
|
||||
break;
|
||||
|
||||
case 47:
|
||||
bg = BG_LIGHTGRAY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
log(LOG_DEBUG,'fg:'+fg+', bg:'+bg+' i:'+i);
|
||||
log(LOG_DEBUG,'ansi:'+JSON.stringify(ansi));
|
||||
}
|
||||
|
||||
attr = bg + fg + i;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (debug)
|
||||
log(LOG_DEBUG,'= Current attr:'+attr);
|
||||
|
||||
/* parse absolute character position */
|
||||
var m = line.match(/^\x1b\[(\d*);?(\d*)[Hf]/);
|
||||
if (m !== null) {
|
||||
line = line.substr(m.shift().length);
|
||||
|
||||
if (m.length === 0) {
|
||||
x = 0;
|
||||
y = 0;
|
||||
|
||||
} else {
|
||||
if(m[0])
|
||||
y = Number(m.shift())-1;
|
||||
|
||||
if(m[0])
|
||||
x = Number(m.shift())-1;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ignore a bullshit sequence */
|
||||
var n = line.match(/^\x1b\[\?7h/);
|
||||
if (n !== null) {
|
||||
line = line.substr(n.shift().length);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* parse an up positional sequence */
|
||||
var n = line.match(/^\x1b\[(\d*)A/);
|
||||
if (n !== null) {
|
||||
line = line.substr(n.shift().length);
|
||||
|
||||
var chars = n.shift();
|
||||
|
||||
y -= (chars < 1) ? 0 : Number(chars);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* parse a down positional sequence */
|
||||
var n = line.match(/^\x1b\[(\d*)B/);
|
||||
if (n !== null) {
|
||||
line = line.substr(n.shift().length);
|
||||
|
||||
var chars = n.shift();
|
||||
|
||||
y += (chars < 1) ? 0 : Number(chars);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* parse a forward positional sequence */
|
||||
var n = line.match(/^\x1b\[(\d*)C/);
|
||||
if (n !== null) {
|
||||
line = line.substr(n.shift().length);
|
||||
|
||||
var chars = n.shift();
|
||||
|
||||
x += (chars < 1) ? 0 : Number(chars);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* parse a backward positional sequence */
|
||||
var n = line.match(/^\x1b\[(\d*)D/);
|
||||
if (n !== null) {
|
||||
line = line.substr(n.shift().length);
|
||||
|
||||
var chars = n.shift()
|
||||
|
||||
x -= (chars < 1) ? 0 : Number(chars);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* parse a clear screen sequence */
|
||||
var n = line.match(/^\x1b\[2J/);
|
||||
if (n !== null) {
|
||||
line = line.substr(n.shift().length);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* parse save cursor sequence */
|
||||
var n = line.match(/^\x1b\[s/);
|
||||
if (n !== null) {
|
||||
line = line.substr(n.shift().length);
|
||||
saved.x = x;
|
||||
saved.y = y;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* parse restore cursor sequence */
|
||||
var n = line.match(/^\x1b\[u/);
|
||||
if (n !== null) {
|
||||
line = line.substr(n.shift().length);
|
||||
x = saved.x;
|
||||
y = saved.y;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* parse an input field */
|
||||
// Input field 'FIELD;valueTYPE;input char'
|
||||
// @todo remove the trailing ESC \ to end the field, just use a control code ^B \x02 (Start of Text) and ^C \x03
|
||||
var m = line.match(/^\x1b_(([A-Z]+;[0-9a-z]+)([;]?.+)?)\x1b\\/);
|
||||
if (m !== null) {
|
||||
log(LOG_DEBUG,'|-- Got input field: '+JSON.stringify(m));
|
||||
//log(LOG_DEBUG,'|-- + ansi:'+JSON.stringify(ansi));
|
||||
// full string that matched
|
||||
match = m.shift();
|
||||
|
||||
// thus, the rest of the line
|
||||
line = line.substr(match.length);
|
||||
//writeln('rest of line:'+JSON.stringify(line));
|
||||
|
||||
// We are interested in our field match
|
||||
var sos = m.shift().split(';');
|
||||
//writeln('sos:'+JSON.stringify(sos));
|
||||
|
||||
for (var num in sos) {
|
||||
switch (num) {
|
||||
// First value is the field name
|
||||
case '0':
|
||||
field = sos[num];
|
||||
break;
|
||||
|
||||
// Second value is the length/type of the field, nnX nn=size in chars, T=type (lower case)
|
||||
case '1':
|
||||
var c = sos[num].match(/([0-9]+)([a-z])/);
|
||||
if (! c) {
|
||||
log(LOG_ERROR,'SOS FAILED PARSING FIELD LENGTH/TYPE. ['+r+'x'+c+'] '+sos[num]);
|
||||
break;
|
||||
}
|
||||
|
||||
//log(LOG_DEBUG,'SOS ['+r+'x'+c+'] NUM CHARS: '+x[1]+', TYPE: '+x[2]);
|
||||
fieldlen = c[1];
|
||||
fieldtype = c[2];
|
||||
break;
|
||||
|
||||
// Third field is the char to to use
|
||||
case '2':
|
||||
fieldchar = sos[num];
|
||||
break;
|
||||
|
||||
default:
|
||||
log(LOG_ERROR,'IGNORING ADDITIONAL SOS FIELDS. ['+r+'x'+c+'] '+sos[num]);
|
||||
}
|
||||
}
|
||||
|
||||
// If we are padding our field with a char, we need to add that back to line
|
||||
// @todo validate if this goes beyond our width (and if scrolling not enabled)
|
||||
if (fieldlen)
|
||||
line = fieldchar.repeat(fieldlen)+line;
|
||||
|
||||
frame.input_fields.push({
|
||||
type: fieldtype,
|
||||
length: Number(fieldlen),
|
||||
char: fieldchar,
|
||||
name: field,
|
||||
attribute: JSON.parse(JSON.stringify(ansi)),
|
||||
x: Number(x+(xoffset !== undefined ? xoffset : 0)),
|
||||
y: Number(y+(yoffset !== undefined ? yoffset : 0)),
|
||||
value: '',
|
||||
});
|
||||
|
||||
log(LOG_DEBUG,'|-- * IF found at ['+x+'x'+y+'], Type: '+fieldtype+', Name: '+field+', Char: '+fieldchar+', Length: '+fieldlen+', Attribute: '+JSON.stringify(ansi));
|
||||
}
|
||||
|
||||
/* parse dynamic value field */
|
||||
// @todo remove the trailing ESC \ to end the field, just use a control code ie: ^E \x05 (Enquiry) or ^Z \x26 (Substitute)
|
||||
var m = line.match(/^\x1bX(([a-zA-Z._:\-%^;]+[0-9]?;-?[0-9^;]+)([;]?[^;]+)?)\x1b\\/);
|
||||
if (m !== null) {
|
||||
// full string that matched
|
||||
match = m.shift();
|
||||
|
||||
// thus, the rest of the line
|
||||
line = line.substr(match.length);
|
||||
//writeln('rest of line:'+JSON.stringify(line));
|
||||
|
||||
// We are interested in our field match
|
||||
var df = m.shift().split(';');
|
||||
|
||||
log(LOG_DEBUG,'|-- * DF found at ['+x+'x'+y+'], Field: '+df[0]+', Length: '+df[1]+', Pad:'+df[2]);
|
||||
// If we are padding our field with a char, we need to add that back to line
|
||||
// @todo validate if this goes beyond our width (and if scrolling not enabled)
|
||||
line = (df[2] ? df[2] : '_').repeat(Math.abs(df[1]))+line;
|
||||
|
||||
frame.dynamic_fields.push({
|
||||
name: df[0],
|
||||
length: df[1],
|
||||
pad: df[2],
|
||||
x: x+(xoffset !== undefined ? xoffset : 0),
|
||||
y: y+(yoffset !== undefined ? yoffset : 0),
|
||||
value: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/* set character and attribute */
|
||||
var ch = line[0];
|
||||
line = line.substr(1);
|
||||
|
||||
if (debug && debug === y) {
|
||||
log(LOG_DEBUG,'Got char: '+ch);
|
||||
//write(ch);
|
||||
}
|
||||
|
||||
/* validate position */
|
||||
if (y < 0)
|
||||
y = 0;
|
||||
if (x < 0)
|
||||
x = 0;
|
||||
|
||||
if (x >= width) {
|
||||
x = 0;
|
||||
y++;
|
||||
}
|
||||
|
||||
/* set character and attribute */
|
||||
if (! frame.content[y+1])
|
||||
frame.content[y+1]=[];
|
||||
|
||||
if (attr === null)
|
||||
throw new Error('Attribute is null?');
|
||||
|
||||
frame.content[y+1][x+1] = new Char(ch,attr);
|
||||
|
||||
x++;
|
||||
}
|
||||
|
||||
// If we got a BG_BLACK|LIGHTGRAY ESC [0m, but not character, we include it as it resets any background that was going on
|
||||
if ((attr === BG_BLACK|LIGHTGRAY) && frame.content[y+1] && (frame.content[y+1][x].__properties__.attr !== attr))
|
||||
frame.content[y+1][x+1] = new Char(undefined,attr);
|
||||
|
||||
y++;
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
load('ansitex/load/session.js');
|
||||
|
||||
// Our frame object
|
||||
function SessionProtocol() {
|
||||
Session.apply(this,arguments);
|
||||
|
||||
this.settings.MSG_SENDORNOT = '\1n\1h\1GKEY 1 TO SEND, 2 NOT TO SEND';
|
||||
this.settings.MSG_LOGON = '\1n\1h\1GKEY 1 TO LOGON, 2 TO RETURN';
|
||||
this.settings.MSG_SENT = '\1n\1h\1GMESSAGE SENT - KEY # TO CONTINUE';
|
||||
this.settings.MSG_NOTSENT = '\1n\1h\1GMESSAGE NOT SENT - KEY # TO CONTINUE';
|
||||
this.settings.ERR_NO_PARENT = '\1n\1h\1RPARENT FRAME DOESNT EXIST';
|
||||
this.settings.ERR_NOT_IMPLEMENTED = '\1n\1h\1RNOT IMPLEMENTED YET?';
|
||||
this.settings.ERR_ROUTE = '\1n\1h\1WMISTAKE? \1GTRY AGAIN OR TELL US ON *08';
|
||||
this.settings.ERR_METHOD_NOT_EXIST = '\1n\1h\1WMISTAKE? \1GTRY AGAIN OR TELL US ON *08';
|
||||
this.settings.ACCESS_DENIED = '\1n\1h\1RACCESS DENIED. MISTAKE? TRY AGAIN OR TELL US *08';
|
||||
this.settings.ALREADY_MEMBER = '\1n\1h\1RALREADY MEMBER OF CUG'
|
||||
this.settings.INACTIVITY = '\1n\1h\1RINACTIVITY ALERT, DISCONNECT PENDING...';
|
||||
this.settings.INACTIVE = '\1n\1h\1RINACTIVITY DISCONNECT';
|
||||
this.settings.NOACTION = '\1n\1h\1RNO ACTION PERFORMED';
|
||||
this.settings.BASESTAR = '\1N\1G\1H*';
|
||||
this.settings.INVALID_CODE = '\1n\1h\1RINVAID CODE, PLEASE TRY AGAIN **';
|
||||
this.settings.TOKEN_EMAIL = '\1n\1h\1RTOKEN EMAILED TO YOU...';
|
||||
this.settings.TOKEN_SENT = '\1n\1h\1RTOKEN SENT, PLEASE ENTER TOKEN';
|
||||
this.settings.INVALID_EMAIL = '\1n\1h\1RINVAID EMAIL, PLEASE TRY AGAIN *00';
|
||||
this.settings.INVALID_UID = '\1n\1h\1RINVAID USER ID, PLEASE TRY AGAIN *00';
|
||||
this.settings.CANNOT_SEND_TOKEN = '\1n\1h\1RCANNOT SEND VALIDATION CODE, PLEASE TRY AGAIN *00';
|
||||
this.settings.USER_EXISTS = '\1n\1h\1RERROR USER EXISTS, PLEASE TRY AGAIN *00';
|
||||
this.settings.USER_CREATE_ERROR = '\1n\1h\1RERROR CREATING USER, PLEASE TRY AGAIN *00';
|
||||
this.settings.LOGIN_ERROR = '\1n\1h\1RERROR LOGGING IN, PLEASE TRY AGAIN *00';
|
||||
this.settings.CANCEL_MSG = '\1n\1h\1GPRESS 2 TO CANCEL';
|
||||
this.settings.SYS_ERROR = '\1n\1h\1RSYSTEM ERROR DETECTED - TRY AGAIN OR TELL US *08';
|
||||
this.settings.LOADING = '\1n\1h\1Kloading...';
|
||||
this.settings.PROCESSING = '\1n\1h\1Kprocessing...';
|
||||
|
||||
/**
|
||||
* Set the attribute at the current position
|
||||
*/
|
||||
this.attr = function(field) {
|
||||
write('\x1b['+field.i+';'+field.f+';'+field.b+'m');
|
||||
}
|
||||
|
||||
this.baselineClear = function(reposition) {
|
||||
this.cursorSave();
|
||||
this.gotoxy(0,24);
|
||||
this.cleareol();
|
||||
|
||||
if (! reposition)
|
||||
this.cursorRestore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to the baseline.
|
||||
*
|
||||
* @param text
|
||||
* @param reposition
|
||||
*/
|
||||
this.baselineSend = function(text,reposition) {
|
||||
this.cursorSave();
|
||||
this.gotoxy(0,24);
|
||||
write(this.getMessage(text));
|
||||
this.cleareol();
|
||||
|
||||
if (! reposition)
|
||||
this.cursorRestore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn off the cursor
|
||||
*/
|
||||
this.cursorOff = function() {
|
||||
write('\x1b[?25l');
|
||||
write('\x1b[24;0H');
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn on cursor
|
||||
* @param x
|
||||
* @param y
|
||||
*/
|
||||
this.cursorOn = function(x,y) {
|
||||
write('\x1b[?25h');
|
||||
|
||||
if (x && y)
|
||||
this.gotoxy(x,y);
|
||||
}
|
||||
|
||||
this.cursorRestore = function() {
|
||||
write('\x1b[u');
|
||||
}
|
||||
|
||||
this.cursorSave = function() {
|
||||
write('\x1b[s');
|
||||
}
|
||||
|
||||
this.cleareol = function() {
|
||||
write('\x1b[0K');
|
||||
}
|
||||
|
||||
// Field backspace, that leaves the field filler char
|
||||
this.fieldbs = function(char) {
|
||||
write('\x1b[D'+char+'\x1b[D');
|
||||
}
|
||||
|
||||
/**
|
||||
* Position the cursor in a specific location
|
||||
*
|
||||
* @param x
|
||||
* @param y
|
||||
*/
|
||||
this.gotoxy = function(x,y) {
|
||||
write('\x1b['+y+';'+x+'H');
|
||||
}
|
||||
|
||||
this.qrcode = function(qr,subframe) {
|
||||
// SMALL Image
|
||||
var full = ascii(0xdb);
|
||||
var top = ascii(0xdf);
|
||||
var bot = ascii(0xdc);
|
||||
var blank = ' ';
|
||||
|
||||
var qrcode = '';
|
||||
|
||||
/*
|
||||
// Render the top line
|
||||
var line = ascii(27)+'[1;37m'+bot;
|
||||
for (var y = 0; y < qr.size; y++) {
|
||||
line += bot;
|
||||
}
|
||||
qrcode += line+bot+bot+ascii(27)+'[0m'+"\r\n";
|
||||
*/
|
||||
|
||||
// Render the body
|
||||
for (var x = -1; x < qr.size; x=x+2) {
|
||||
line = ascii(27)+'[1;37m'+full;
|
||||
|
||||
for (var y = 0; y < qr.size; y++) {
|
||||
// Top is white
|
||||
if (! ((x===-1)? 0 : qr.getModule(x, y))) {
|
||||
line += (qr.getModule(x+1, y)) ? top : full;
|
||||
|
||||
// Top is black
|
||||
} else {
|
||||
line += (qr.getModule(x+1, y)) ? blank : bot;
|
||||
}
|
||||
}
|
||||
|
||||
qrcode += line+full+ascii(27)+'[0m'+"\r\n";
|
||||
}
|
||||
|
||||
// Render the bottom
|
||||
line = ascii(27)+'[1;37m'+top;
|
||||
for (var y = 0; y < qr.size; y++) {
|
||||
line += top;
|
||||
}
|
||||
qrcode += line+top+ascii(27)+'[0m'+"\r\n";
|
||||
|
||||
ans2bin(fo.parse(qrcode),subframe);
|
||||
subframe.open();
|
||||
subframe.cycle();
|
||||
};
|
||||
}
|
||||
|
||||
SessionProtocol.prototype = Session.prototype;
|
||||
SessionProtocol.prototype.constructor = SessionProtocol;
|
650
load/session/viewdata.js
Normal file
650
load/session/viewdata.js
Normal file
@ -0,0 +1,650 @@
|
||||
const SESSION_VIEWDATA = (1<<2);
|
||||
var CONTENT_EXT = 'bin';
|
||||
|
||||
var FRAME_WIDTH = 40;
|
||||
var FRAME_HEIGHT = 22;
|
||||
var FRAME_PROVIDER_LENGTH = 23;
|
||||
var FRAME_PAGE_LENGTH = 11;
|
||||
var FRAME_COST_LENGTH = 6;
|
||||
var FRAME_ATTR_LENGTH = 1; // Space that an attribute takes
|
||||
|
||||
const VIEWDATA_LEFT = '\x08';
|
||||
const VIEWDATA_RIGHT = '\x09';
|
||||
const VIEWDATA_DOWN = '\x0a'; // \n
|
||||
const VIEWDATA_UP = '\x0b';
|
||||
const VIEWDATA_CLS = '\x0c';
|
||||
const VIEWDATA_CR = '\x0d'; // \r
|
||||
const VIEWDATA_CON = '\x11';
|
||||
const VIEWDATA_COFF = '\x14';
|
||||
const VIEWDATA_HOME = '\x1e';
|
||||
|
||||
const VIEWDATA_BLINK = '\x48';
|
||||
const VIEWDATA_STEADY = '\x49';
|
||||
const VIEWDATA_NORMAL = '\x4c';
|
||||
const VIEWDATA_DOUBLE = '\x4d';
|
||||
const VIEWDATA_CONCEAL = '\x58';
|
||||
const VIEWDATA_BLOCKS = '\x59';
|
||||
const VIEWDATA_SEPARATED = '\x5a';
|
||||
const VIEWDATA_BLACKBACK = '\x5c';
|
||||
const VIEWDATA_NEWBACK = '\x5d';
|
||||
const VIEWDATA_HOLD = '\x5e';
|
||||
const VIEWDATA_REVEAL = '\x5f';
|
||||
|
||||
const VIEWDATA_RED = '\x41';
|
||||
const VIEWDATA_GREEN = '\x42';
|
||||
const VIEWDATA_YELLOW = '\x43'; // C
|
||||
const VIEWDATA_BLUE = '\x44';
|
||||
const VIEWDATA_MAGENTA = '\x45';
|
||||
const VIEWDATA_CYAN = '\x46';
|
||||
const VIEWDATA_WHITE = '\x47';
|
||||
|
||||
const VIEWDATA_MOSIAC_RED = '\x51';
|
||||
const VIEWDATA_MOSIAC_GREEN = '\x52';
|
||||
const VIEWDATA_MOSIAC_YELLOW = '\x53';
|
||||
const VIEWDATA_MOSIAC_BLUE = '\x54';
|
||||
const VIEWDATA_MOSIAC_MAGENTA = '\x55';
|
||||
const VIEWDATA_MOSIAC_CYAN = '\x56';
|
||||
const VIEWDATA_MOSIAC_WHITE = '\x57'; // W
|
||||
|
||||
/* BINARY DUMP LEVEL 1 ATTRIBUTES */
|
||||
const VIEWDATA_BIN_RED = '\x01';
|
||||
const VIEWDATA_BIN_GREEN = '\x02';
|
||||
const VIEWDATA_BIN_YELLOW = '\x03';
|
||||
const VIEWDATA_BIN_BLUE = '\x04';
|
||||
const VIEWDATA_BIN_MAGENTA = '\x05';
|
||||
const VIEWDATA_BIN_CYAN = '\x06';
|
||||
const VIEWDATA_BIN_WHITE = '\x07';
|
||||
|
||||
/**
|
||||
* ViewData characters are 7bit (0x00-0x7f)
|
||||
*
|
||||
* Chars 0x00-0x1f are control characters (display attributes) and are sent to the terminal with 0x1b
|
||||
* + 0x00-0x07 are foreground colors
|
||||
* + 0x08-0x09 flash/steady
|
||||
* + 0x0a-0x0b end/start box (?) *
|
||||
* + 0x0c-0x0d normal/double height
|
||||
* + 0x0e-0x0f double width (?) *
|
||||
* + 0x10-0x17 are foreground graphics (mosiac) colors
|
||||
* + 0x18/0x1f conceal/reveal
|
||||
* + 0x19-0x1a solid/seperated graphics
|
||||
* + 0x1b unused
|
||||
* + 0x1c-1x1d Black/New Background (new background converts color foreground to background)
|
||||
* + 0x1e-0x1f graphics hold/release (enables changing color and repeats previous graphics char)
|
||||
* Chars 0x20-0x7f are normal printed ASCII chars
|
||||
* Chars 0x20-0x3f & 0x60-0x7f when activated with a MOSIAC color sends a 2x3 pixel character
|
||||
*
|
||||
* We can map these into cga_defs with the following amendments:
|
||||
* 0x00-0x0f = foreground/background colors (4 bits) (8 foreground/8 background colors)
|
||||
* 0x10 - mosiac (bit 4)
|
||||
* 0x20 - conceal (bit 5)
|
||||
* 0x40 - seperated graphics (bit 6)
|
||||
* 0x80 - flash (bit 7)
|
||||
* 0x100 - double height (bit 8)
|
||||
* 0x200 - hold (bit 9)
|
||||
* 0x400 - new background (bits 10/11)
|
||||
* 0x800 - black background (bits 10/11)
|
||||
* 0xc00 - unused (bits 10/11)
|
||||
* bits (12-15) unused
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
var MOSIAC = 0x10;
|
||||
|
||||
// Toggles
|
||||
var CONCEAL = 0x20;
|
||||
var REVEAL = 0x2000; // @temp Turns off Conceal
|
||||
|
||||
var SEPARATED = 0x40;
|
||||
var BLOCKS = 0x4000; // @temp Turns off Separated
|
||||
|
||||
var STEADY = 0x8000; // @temp (turn off flash)
|
||||
|
||||
var DOUBLE = 0x100;
|
||||
var NORMAL = 0x1000; // @temp Turns off Double Height
|
||||
|
||||
var HOLD = 0x200;
|
||||
var RELEASE = 0x20000; // @temp turns off Hold
|
||||
|
||||
var NEWBACK = 0x400;
|
||||
var BLACKBACK = 0x800;
|
||||
|
||||
/**
|
||||
* This function converts ANSI text into an array of attributes
|
||||
*
|
||||
* @param contents - Our ANSI content to convert
|
||||
* @param width - The width before wrapping to the next line
|
||||
* @param yoffset - fields offset as discovered
|
||||
* @param xoffset - fields offset as discovered
|
||||
* @param debug - Enable debug mode
|
||||
*/
|
||||
function rawtoattrs(contents,width,yoffset,xoffset,debug) {
|
||||
if (debug)
|
||||
writeln('DEBUG active: '+debug);
|
||||
|
||||
lines = (''+contents).split(/\r\n/);
|
||||
|
||||
var i = 0;
|
||||
var bg = BG_BLACK;
|
||||
var fg = LIGHTGRAY;
|
||||
var attr = fg + bg + i;
|
||||
|
||||
// Attribute state on a new line
|
||||
var new_line = attr;
|
||||
|
||||
var y = 0;
|
||||
|
||||
var frame = {
|
||||
content: [],
|
||||
dynamic_fields: [],
|
||||
input_fields: [],
|
||||
};
|
||||
|
||||
// @todo temp hack, rework ansi variable - perhaps have a function that converts an attribute back into an ANSI sequence
|
||||
var ansi = { i: 0, f: 37, b: 40 };
|
||||
|
||||
while (lines.length > 0) {
|
||||
var x = 0;
|
||||
var line = lines.shift();
|
||||
|
||||
if ((debug !== undefined) && (y > debug)) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
log(LOG_DEBUG,'y:'+y);
|
||||
log(LOG_DEBUG,'line:'+line);
|
||||
write('y:'+y+', line:'+line);
|
||||
}
|
||||
|
||||
while (line.length > 0) {
|
||||
if (x >= width) {
|
||||
x = 0;
|
||||
// Each new line, we reset the attrs
|
||||
attr = new_line;
|
||||
y++;
|
||||
}
|
||||
//writeln('next ch:'+line[0].charCodeAt(0));
|
||||
|
||||
/* parse control codes */
|
||||
var m = line.match(/^([\x00-\x1f])/);
|
||||
if (m !== null) {
|
||||
line = line.substr(m[0].length);
|
||||
attr = 0;
|
||||
|
||||
match = m.shift().charCodeAt(0);
|
||||
|
||||
/*
|
||||
writeln('- match:'+match);
|
||||
|
||||
writeln('- match 0x0f:'+(match & 0x0f));
|
||||
if (match & 0x10) {
|
||||
writeln(' - got mosiac');
|
||||
attr += MOSIAC;
|
||||
}
|
||||
|
||||
*/
|
||||
//if (match < 0x0f) {
|
||||
//switch(match & 0x07) {
|
||||
switch(match) {
|
||||
case 0x00:
|
||||
attr += BLACK;
|
||||
break;
|
||||
case 0x01:
|
||||
attr += RED;
|
||||
break;
|
||||
case 0x02:
|
||||
attr += GREEN;
|
||||
break;
|
||||
case 0x03:
|
||||
attr += YELLOW;
|
||||
break;
|
||||
case 0x04:
|
||||
attr += BLUE;
|
||||
break;
|
||||
case 0x05:
|
||||
attr += MAGENTA;
|
||||
break;
|
||||
case 0x06:
|
||||
attr += CYAN;
|
||||
break;
|
||||
case 0x07:
|
||||
attr += LIGHTGRAY;
|
||||
break;
|
||||
case 0x08:
|
||||
attr = BLINK;
|
||||
break;
|
||||
case 0x09:
|
||||
attr = STEADY;
|
||||
break;
|
||||
/*
|
||||
case 0x0a:
|
||||
//attr = ENDBOX; // End Box (Unused?)
|
||||
break;
|
||||
case 0x0b:
|
||||
//attr = STARTBOX; // Start Box (Unused?)
|
||||
break;
|
||||
*/
|
||||
case 0x0c:
|
||||
//attr &= ~DOUBLE;
|
||||
attr = NORMAL;
|
||||
break;
|
||||
case 0x0d:
|
||||
attr = DOUBLE;
|
||||
break;
|
||||
case 0x0e:
|
||||
attr = NORMAL; // @todo Double Width (Unused)?
|
||||
break;
|
||||
case 0x0f:
|
||||
attr = NORMAL; // @todo Double Width (Unused?)
|
||||
break;
|
||||
case 0x10:
|
||||
attr = MOSIAC|BLACK;
|
||||
break;
|
||||
case 0x11:
|
||||
attr += MOSIAC|RED;
|
||||
break;
|
||||
case 0x12:
|
||||
attr += MOSIAC|GREEN;
|
||||
break;
|
||||
case 0x13:
|
||||
attr += MOSIAC|YELLOW;
|
||||
break;
|
||||
case 0x14:
|
||||
attr += MOSIAC|BLUE;
|
||||
break;
|
||||
case 0x15:
|
||||
attr += MOSIAC|MAGENTA;
|
||||
break;
|
||||
case 0x16:
|
||||
attr += MOSIAC|CYAN;
|
||||
break;
|
||||
case 0x17:
|
||||
attr += MOSIAC|LIGHTGRAY;
|
||||
break;
|
||||
case 0x18:
|
||||
attr = CONCEAL;
|
||||
break;
|
||||
case 0x19:
|
||||
attr = BLOCKS;
|
||||
break;
|
||||
case 0x1a:
|
||||
attr = SEPARATED;
|
||||
break;
|
||||
/*
|
||||
case 0x1b:
|
||||
//attr = NORMAL; // CSI
|
||||
break;
|
||||
*/
|
||||
case 0x1c:
|
||||
attr = BLACKBACK; // Black Background
|
||||
break;
|
||||
case 0x1d:
|
||||
attr = NEWBACK; // New Background
|
||||
break;
|
||||
case 0x1e:
|
||||
attr = HOLD; // Mosiac Hold
|
||||
break;
|
||||
case 0x1f:
|
||||
attr = RELEASE; // Mosiac Release
|
||||
break;
|
||||
|
||||
// Catch all for other codes
|
||||
default:
|
||||
attr = 0xff00;
|
||||
}
|
||||
|
||||
if (debug)
|
||||
writeln(' - got control code:'+attr+'['+y+','+x+'] - length:'+attr.length);
|
||||
|
||||
store(x++,y,null,attr);
|
||||
attr = undefined;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* parse an input field */
|
||||
// Input field 'FIELD;valueTYPE;input char'
|
||||
// @todo remove the trailing ESC \ to end the field, just use a control code ^B \x02 (Start of Text) and ^C \x03
|
||||
var m = line.match(/^\x1b_(([A-Z]+;[0-9a-z]+)([;]?.+)?)\x1b\\/);
|
||||
if (m !== null) {
|
||||
log(LOG_DEBUG,'Got input field: '+JSON.stringify(m));
|
||||
log(LOG_DEBUG,'ansi:'+JSON.stringify(ansi));
|
||||
// full string that matched
|
||||
match = m.shift();
|
||||
|
||||
// thus, the rest of the line
|
||||
line = line.substr(match.length);
|
||||
//writeln('rest of line:'+JSON.stringify(line));
|
||||
|
||||
// We are interested in our field match
|
||||
var sos = m.shift().split(';');
|
||||
//writeln('sos:'+JSON.stringify(sos));
|
||||
|
||||
for (var num in sos) {
|
||||
switch (num) {
|
||||
// First value is the field name
|
||||
case '0':
|
||||
field = sos[num];
|
||||
break;
|
||||
|
||||
// Second value is the length/type of the field, nnX nn=size in chars, T=type (lower case)
|
||||
case '1':
|
||||
var c = sos[num].match(/([0-9]+)([a-z])/);
|
||||
if (! c) {
|
||||
log(LOG_ERROR,'SOS FAILED PARSING FIELD LENGTH/TYPE. ['+r+'x'+c+'] '+sos[num]);
|
||||
break;
|
||||
}
|
||||
|
||||
//log(LOG_DEBUG,'SOS ['+r+'x'+c+'] NUM CHARS: '+x[1]+', TYPE: '+x[2]);
|
||||
fieldlen = c[1];
|
||||
fieldtype = c[2];
|
||||
break;
|
||||
|
||||
// Third field is the char to to use
|
||||
case '2':
|
||||
fieldchar = sos[num];
|
||||
break;
|
||||
|
||||
default:
|
||||
log(LOG_ERROR,'IGNORING ADDITIONAL SOS FIELDS. ['+r+'x'+c+'] '+sos[num]);
|
||||
}
|
||||
}
|
||||
|
||||
// If we are padding our field with a char, we need to add that back to line
|
||||
// @todo validate if this goes beyond our width (and if scrolling not enabled)
|
||||
if (fieldlen)
|
||||
line = fieldchar.repeat(fieldlen)+line;
|
||||
|
||||
frame.input_fields.push({
|
||||
type: fieldtype,
|
||||
length: Number(fieldlen),
|
||||
char: fieldchar,
|
||||
name: field,
|
||||
attribute: JSON.parse(JSON.stringify(ansi)),
|
||||
x: Number(x+(xoffset !== undefined ? xoffset : 0)),
|
||||
y: Number(y+(yoffset !== undefined ? yoffset : 0)),
|
||||
value: '',
|
||||
});
|
||||
|
||||
log(LOG_DEBUG,'input_field:'+JSON.stringify(frame.input_fields.last));
|
||||
}
|
||||
|
||||
/* parse dynamic value field */
|
||||
// @todo remove the trailing ESC \ to end the field, just use a control code ie: ^E \x05 (Enquiry) or ^Z \x26 (Substitute)
|
||||
var m = line.match(/^\x1bX(([a-zA-Z._:^;]+[0-9]?;-?[0-9^;]+)([;]?[^;]+)?)\x1b\\/);
|
||||
if (m !== null) {
|
||||
// full string that matched
|
||||
match = m.shift();
|
||||
|
||||
// thus, the rest of the line
|
||||
line = line.substr(match.length);
|
||||
//writeln('rest of line:'+JSON.stringify(line));
|
||||
|
||||
// We are interested in our field match
|
||||
var df = m.shift().split(';');
|
||||
|
||||
log(LOG_DEBUG,'- DF found at ['+x+'x'+y+'], Field: '+df[0]+', Length: '+df[1]+', Pad:'+df[2]);
|
||||
// If we are padding our field with a char, we need to add that back to line
|
||||
// @todo validate if this goes beyond our width (and if scrolling not enabled)
|
||||
line = (df[2] ? df[2] : '_').repeat(Math.abs(df[1]))+line;
|
||||
|
||||
frame.dynamic_fields.push({
|
||||
name: df[0],
|
||||
length: df[1],
|
||||
pad: df[2],
|
||||
x: x+(xoffset !== undefined ? xoffset : 0),
|
||||
y: y+(yoffset !== undefined ? yoffset : 0),
|
||||
value: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/* set character and attribute */
|
||||
var ch = line[0];
|
||||
line = line.substr(1);
|
||||
|
||||
if (debug && (debug === y)) {
|
||||
writeln('y:'+y+', x:'+x+', ch:'+ch);
|
||||
}
|
||||
|
||||
/* validate position */
|
||||
if (y < 0)
|
||||
y = 0;
|
||||
if (x < 0)
|
||||
x = 0;
|
||||
|
||||
store(x,y,ch,undefined);
|
||||
x++;
|
||||
}
|
||||
|
||||
// Each new line, we reset the attrs
|
||||
attr = undefined;
|
||||
y++;
|
||||
}
|
||||
|
||||
return frame;
|
||||
|
||||
function store(x,y,ch,attr) {
|
||||
/* set character and attribute */
|
||||
if (! frame.content[y+1])
|
||||
frame.content[y+1]=[];
|
||||
|
||||
frame.content[y+1][x+1] = new Char(ch,attr,SESSION_EXT);
|
||||
}
|
||||
}
|
||||
|
||||
load('ansitex/load/session.js');
|
||||
|
||||
// Our frame object
|
||||
function SessionProtocol() {
|
||||
Session.apply(this,arguments);
|
||||
|
||||
this.settings.MSG_SENDORNOT = ascii(27)+'BKEY 1 TO SEND, 2 NOT TO SEND';
|
||||
this.settings.MSG_LOGON = ascii(27)+'BKEY 1 TO LOGON, 2 TO RETURN';
|
||||
this.settings.MSG_SENT = ascii(27)+'BMESSAGE SENT - KEY _ TO CONTINUE';
|
||||
this.settings.MSG_NOTSENT = ascii(27)+'BMESSAGE NOT SENT - KEY _ TO CONTINUE';
|
||||
this.settings.ERR_NO_PARENT = ascii(27)+'APARENT FRAME DOESNT EXIST';
|
||||
this.settings.ERR_NOT_IMPLEMENTED = ascii(27)+'ANOT IMPLEMENTED YET?';
|
||||
this.settings.ERR_ROUTE = ascii(27)+'GMISTAKE?'+ascii(27)+'BTRY AGAIN OR TELL US ON *08';
|
||||
this.settings.ERR_METHOD_NOT_EXIST = ascii(27)+'GMISTAKE?'+ascii(27)+'BTRY AGAIN OR TELL US ON *08';
|
||||
this.settings.ACCESS_DENIED = ascii(27)+'AACCESS DENIED.';
|
||||
this.settings.ALREADY_MEMBER = ascii(27)+'AALREADY MEMBER OF CUG'
|
||||
this.settings.INACTIVITY = ascii(27)+'AINACTIVITY ALERT, DISCONNECT PENDING...';
|
||||
this.settings.INACTIVE = ascii(27)+'AINACTIVITY DISCONNECT';
|
||||
this.settings.NOACTION = ascii(27)+'ANO ACTION PERFORMED';
|
||||
this.settings.BASESTAR = ascii(27)+'B*';
|
||||
this.settings.INVALID_CODE = ascii(27)+'AINVAID CODE, PLEASE TRY AGAIN **';
|
||||
this.settings.TOKEN_EMAIL = ascii(27)+'ATOKEN EMAILED TO YOU...';
|
||||
this.settings.TOKEN_SENT = ascii(27)+'ATOKEN SENT, PLEASE ENTER TOKEN';
|
||||
this.settings.INVALID_EMAIL = ascii(27)+'AINVAID EMAIL, PLEASE TRY AGAIN *00';
|
||||
this.settings.INVALID_UID = ascii(27)+'AINVAID USER ID, PLEASE TRY AGAIN *00';
|
||||
this.settings.CANNOT_SEND_TOKEN = ascii(27)+'ACANNOT SEND VALIDATION CODE, PLEASE TRY AGAIN *00';
|
||||
this.settings.USER_EXISTS = ascii(27)+'AERROR USER EXISTS, PLEASE TRY AGAIN *00';
|
||||
this.settings.USER_CREATE_ERROR = ascii(27)+'AERROR CREATING USER, PLEASE TRY AGAIN *00';
|
||||
this.settings.LOGIN_ERROR = ascii(27)+'AERROR LOGGING IN, PLEASE TRY AGAIN *00';
|
||||
this.settings.CANCEL_MSG = ascii(27)+'BPRESS 2 TO CANCEL';
|
||||
this.settings.SYS_ERROR = ascii(27)+'ASYS ERR, TRY AGAIN OR TELL US ON *08';
|
||||
this.settings.LOADING = ascii(27)+'Cloading...';
|
||||
this.settings.PROCESSING = ESC+VIEWDATA_YELLOW+'processing...';
|
||||
|
||||
var blp = 0; // Length of data on the bottom line
|
||||
|
||||
/**
|
||||
* Set the attribute at the current position
|
||||
*/
|
||||
this.attr = function(field) {
|
||||
//NOOP - the terminal takes care of this
|
||||
}
|
||||
|
||||
this.baselineClear = function(reposition) {
|
||||
msg = '';
|
||||
|
||||
log(LOG_DEBUG,'- Clear Bottom Line ['+blp+'] - reposition ['+reposition+']');
|
||||
|
||||
write_raw(VIEWDATA_HOME+VIEWDATA_UP+msg+
|
||||
((blp > msg.length)
|
||||
? (' '.repeat(2+blp-msg.length)+(reposition ? VIEWDATA_HOME+VIEWDATA_UP+VIEWDATA_RIGHT.repeat(msg.length) : ''))
|
||||
: '')
|
||||
);
|
||||
|
||||
blp = msg.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to the baseline.
|
||||
*
|
||||
* @param text
|
||||
* @param reposition
|
||||
*/
|
||||
this.baselineSend = function(text,reposition) {
|
||||
var msg = this.getMessage(text);
|
||||
var x = this.strlen(msg);
|
||||
|
||||
log(LOG_DEBUG,'- Bottom Line ['+msg+'] ('+x+') - reposition ['+reposition+'] BLP:'+blp);
|
||||
|
||||
write_raw(VIEWDATA_HOME+VIEWDATA_UP+msg+
|
||||
((blp > x)
|
||||
? (' '.repeat(blp-x)+(reposition ? VIEWDATA_HOME+VIEWDATA_UP+VIEWDATA_RIGHT.repeat(x) : ''))
|
||||
: '')
|
||||
);
|
||||
|
||||
blp = x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn off the cursor
|
||||
*/
|
||||
this.cursorOff = function() {
|
||||
write_raw(VIEWDATA_COFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn on cursor
|
||||
* @param x
|
||||
* @param y
|
||||
*/
|
||||
this.cursorOn = function(x,y) {
|
||||
write_raw(VIEWDATA_CON);
|
||||
|
||||
if (x && y)
|
||||
this.gotoxy(x,y);
|
||||
}
|
||||
|
||||
// Field backspace, that leaves the field filler char
|
||||
this.fieldbs = function(char) {
|
||||
log(LOG_DEBUG,'- Field backspace with char:'+char);
|
||||
write_raw(VIEWDATA_LEFT+char+VIEWDATA_LEFT);
|
||||
}
|
||||
|
||||
this.gotoxy = function(x,y) {
|
||||
log(LOG_DEBUG,'- Moving cursor to y:'+y+', x:'+x);
|
||||
|
||||
// @todo This could be optimised to go the shortest route
|
||||
write_raw(VIEWDATA_HOME);
|
||||
|
||||
if (x > 0)
|
||||
write_raw(VIEWDATA_RIGHT.repeat(x-1));
|
||||
|
||||
if (y > 0)
|
||||
write_raw(VIEWDATA_DOWN.repeat(y-1));
|
||||
}
|
||||
|
||||
this.strlen = function(str) {
|
||||
return str.replace(/\x1b/g,'').length;
|
||||
};
|
||||
|
||||
this.qrcode = function(qr) {
|
||||
// Render the body
|
||||
var qrcode = VIEWDATA_HOME+VIEWDATA_DOWN.repeat(5);
|
||||
var offset = this.settings.FRAME_WIDTH-Math.ceil(qr.size/2)-1;
|
||||
|
||||
for (var x = -1; x < qr.size; x=x+3) {
|
||||
var line = VIEWDATA_RIGHT.repeat(offset ? offset-1 : 0)+ESC+VIEWDATA_MOSIAC_WHITE;
|
||||
|
||||
for (var y = -1; y < qr.size; y=y+2) {
|
||||
var char = 0;
|
||||
|
||||
//TL
|
||||
char |= ((x===-1) || (y===-1) || ! qr.getModule(x,y)) ? (1<<0) : (0<<0);
|
||||
//TR
|
||||
char |= ((x===-1) || (y === qr.size-1) || ! qr.getModule(x,y+1)) ? (1<<1) : (0<<1);
|
||||
//ML
|
||||
char |= ((y===-1) || ! qr.getModule(x+1,y)) ? (1<<2) : (0<<2);
|
||||
//MR
|
||||
char |= ((y === qr.size-1) || ! qr.getModule(x+1,y+1)) ? (1<<3) : (0<<3);
|
||||
//BL
|
||||
char |= ((x===qr.size-2) || (y===-1) || ! qr.getModule(x+2,y)) ? (1<<4) : (0<<4);
|
||||
//BR
|
||||
char |= ((x===qr.size-2) || (y === qr.size-1) || ! qr.getModule(x+2,y+1)) ? (1<<5) : (0<<5);
|
||||
|
||||
char += 0x20;
|
||||
if (char > 0x3f)
|
||||
char += 0x20;
|
||||
|
||||
line += ascii(char);
|
||||
}
|
||||
|
||||
// Render the right column
|
||||
if (y%2)
|
||||
line += '\x35';
|
||||
|
||||
repeat_count = this.settings.FRAME_WIDTH-Math.ceil(qr.size/2)-offset-(offset ? 1 : 2)-(y%2 === 1 ? 0 : 1);
|
||||
|
||||
qrcode += line+' '.repeat(repeat_count > 0 ? repeat_count : 0);
|
||||
|
||||
// To fix some terminals where moving right from col 40 doesnt advance to col 1 on the next line
|
||||
qrcode +=VIEWDATA_LEFT+VIEWDATA_CR+VIEWDATA_DOWN;
|
||||
}
|
||||
|
||||
log(LOG_DEBUG,'WIDTH:'+this.settings.FRAME_WIDTH);
|
||||
log(LOG_DEBUG,'QR :'+(Math.ceil(qr.size/2)+1));
|
||||
log(LOG_DEBUG,'OFF :'+offset);
|
||||
log(LOG_DEBUG,'Y :'+(y%2 ? 0 : 1));
|
||||
log(LOG_DEBUG,'X :'+(x%3 ? 0 : 1));
|
||||
|
||||
// Render the bottom
|
||||
if (x%3) {
|
||||
line = VIEWDATA_RIGHT.repeat(offset ? offset-1 : 0)+ESC+VIEWDATA_MOSIAC_WHITE;
|
||||
|
||||
for (var y = 0; y < qr.size; y=y+2) {
|
||||
line += '\x23';
|
||||
}
|
||||
|
||||
// Render the right column
|
||||
if (y%2 === 0) {
|
||||
line += '\x21';
|
||||
}
|
||||
|
||||
qrcode += line+' '.repeat(repeat_count > 0 ? repeat_count : 0);
|
||||
}
|
||||
|
||||
write_raw(qrcode);
|
||||
};
|
||||
|
||||
/*
|
||||
this.save=function() {
|
||||
file = system.mods_dir+'ansitex/text/'+this.page+'.tex';
|
||||
w = new File(file);
|
||||
if (! w.open('w')) {
|
||||
log(LOG_ERROR,'! ERROR: Unable to create TEX file for '+this.page);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
w.write(JSON.stringify(this));
|
||||
w.close();
|
||||
|
||||
log(LOG_DEBUG,'Saved file: '+this.page+'.tex');
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
function videotex(data) {
|
||||
var output = '';
|
||||
//$output .= ($byte < 32) ? ESC.chr($byte+64) : chr($byte);
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
output += (data.charCodeAt(i) < 32) ? "\x1b"+String.fromCharCode(data.charCodeAt(i)+64) : String.fromCharCode(data.charCodeAt(i));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
SessionProtocol.prototype = Session.prototype;
|
||||
SessionProtocol.prototype.constructor = SessionProtocol;
|
1070
load/windows.js
Normal file
1070
load/windows.js
Normal file
File diff suppressed because it is too large
Load Diff
20
logon.js
Normal file
20
logon.js
Normal file
@ -0,0 +1,20 @@
|
||||
require("sbbsdefs.js", 'SS_RLOGIN');
|
||||
//require("nodedefs.js", 'NODE_QUIET');
|
||||
|
||||
// To force all users to the ansitex shell
|
||||
// @todo Make a ini config setting for this
|
||||
user.command_shell = 'ansitex';
|
||||
|
||||
// Disable System Info and ?
|
||||
system.settings |= (SYS_NOSYSINFO | SYS_NONODELIST);
|
||||
|
||||
// @note: Unable to suppress "Logging on to <BBS> as <USER>..."
|
||||
|
||||
// Need to suppress Search for new messages & files
|
||||
user.settings &= ~(USER_ASK_SSCAN | USER_ASK_NSCAN | USER_ANFSCAN | USER_PAUSE | USER_NO_EXASCII | USER_COLDKEYS);
|
||||
|
||||
// Enable ANSI and some other settings
|
||||
user.settings |= (USER_AUTOTERM | USER_ANSI | USER_COLOR);
|
||||
|
||||
// Disable Chatting
|
||||
user.chat_settings |= (CHAT_NOPAGE | CHAT_NOACT);
|
1
text/1a
Normal file
1
text/1a
Normal file
@ -0,0 +1 @@
|
||||
{"version":2,"cost":0,"key":[null,11,2,null,null,null,null,97,5162,516],"dynamic_fields":[{"name":"DATE:%Y-%b-%d","length":11,"pad":" ","x":30,"y":6},{"name":"nodeid","length":8,"pad":" ","x":33,"y":7}]}
|
1
text/980a
Normal file
1
text/980a
Normal file
@ -0,0 +1 @@
|
||||
{"version":2,"key":[0,0,null,null,null,null,null,null,null,null]}
|
1
text/981a
Normal file
1
text/981a
Normal file
@ -0,0 +1 @@
|
||||
{"version":2,"attrs":"1","key":[980,"register",null,null,null,null,null,null,null,null],"dynamic_fields":[{"name":"nodeid","length":8,"pad":" ","x":33,"y":2}],"input_fields":[{"type":"t","length":25,"char":".","name":"EMAIL","x":13,"y":11,"value":""},{"type":"t","length":15,"char":".","name":"USER","x":13,"y":12,"value":""},{"type":"p","length":15,"char":".","name":"PASS","x":13,"y":13,"value":""},{"type":"t","length":25,"char":".","name":"FULLNAME","x":13,"y":14,"value":""},{"type":"t","length":6,"char":".","name":"TOKEN","x":13,"y":15,"value":""}]}
|
1
text/981b
Normal file
1
text/981b
Normal file
@ -0,0 +1 @@
|
||||
{"version":2,"key":[0,null,null,null,null,null,null,null,null,null]}
|
1
text/983a
Normal file
1
text/983a
Normal file
@ -0,0 +1 @@
|
||||
{"version":2,"type":"i","key":[0,null,null,null,null,null,null,null,null,null],"dynamic_fields":[{"name":"nodeid","length":8,"pad":" ","x":33,"y":2}]}
|
1
text/98a
Normal file
1
text/98a
Normal file
@ -0,0 +1 @@
|
||||
{"version":2,"attrs":12,"key":[null,"login",null,null,null,null,null,null,null,null],"dynamic_fields":[{"name":"nodeid","length":8,"pad":" ","x":33,"y":2}],"input_fields":[{"type":"t","length":15,"char":".","name":"USER","y":11,"x":16,"attribute":{},"value":""},{"type":"p","length":15,"char":".","name":"PASS","y":12,"x":16,"attribute":{},"value":""}]}
|
1
text/98b
Normal file
1
text/98b
Normal file
@ -0,0 +1 @@
|
||||
{"version":2,"key":[1,null,null,null,null,null,null,null,null,null],"dynamic_fields":[{"name":"nodeid","length":8,"pad":" ","x":33,"y":2}]}
|
1
text/998a
Normal file
1
text/998a
Normal file
@ -0,0 +1 @@
|
||||
{"version":2,"key":[0,null,null,null,null,null,null,null,null,null]}
|
1
text/999a
Normal file
1
text/999a
Normal file
@ -0,0 +1 @@
|
||||
{"version":2,"key":[0,null,null,null,null,null,null,null,null,null]}
|
1
text/999b
Normal file
1
text/999b
Normal file
@ -0,0 +1 @@
|
||||
{"version":2,"key":[0,null,null,null,null,null,null,null,null,null]}
|
1
text/99a
Normal file
1
text/99a
Normal file
@ -0,0 +1 @@
|
||||
{"version":2,"key":[0,null,null,null,null,null,null,null,null,null]}
|
1
text/99b
Normal file
1
text/99b
Normal file
@ -0,0 +1 @@
|
||||
{"version":2,"attrs":"15","key":[null,null,null,null,null,null,null,null,null,null]}
|
1
text/tex/198a
Normal file
1
text/tex/198a
Normal file
@ -0,0 +1 @@
|
||||
{"version":1,"frame":198,"index":"a","owner":1,"cost":0,"content":"G1swbRtbNzZDG1sxbS4bWzBtDQogG1szM20bWG1zZ19ncnBfbmFtZTs0MBtcG1szMEMbWzM3bRtbMW3awr/Cv7+zG1swbQ0KIBtbMzZtG1htc2dfYXJlYV9hcmVhdGFnOzQwG1wbWzMwQxtbMzdtsyCzwrSzsw0KIBtbMTszNm0bWG1zZ19hcmVhX2Rlc2M7NDAbXBtbMzBDG1sxOzM3bVN1bW1hcnkbWzBtDQobWzE7MzBtxMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMSzxMTExMTExMTExMTExMTExMTExMTExMTExMTExBtbMG0NCiAbWERBVEVUSU1FOzI0G1wbWzI1QxtbMTszMG2zG1swbSAbWzFtTmV3IE1lc3NhZ2VzIHRvIFlvdRtbMG06IBtbMTszMW0bWG1zZ19hcmVhX25ld3RvbWU7LTUbXBtbMG0NChtbNTBDG1sxOzMwbbMbWzBtICAgICAbWzFtVW5yZWFkIE1lc3NhZ2VzG1swbTogG1sxOzMxbRtYbXNnX2FyZWFfbmV3Oy01G1wbWzBtDQogG1sxOzMybTEbWzM3bSBGaXJzdCB0byB5b3UgICAbWzM0bRtYbXNnX2FyZWFfbXNnb3RvbWVfZGF0ZTszMRtcG1swbSAbWzE7MzBtsxtbMG0gICAgICAbWzFtVG90YWwgTWVzc2FnZXMbWzBtOiAbWzE7MzFtG1htc2dfYXJlYV90b3RhbDstNRtcG1swbQ0KIBtbMTszMm0yG1swbSAbWzFtRmlyc3QgdW5yZWFkICAgG1sxOzM0bRtYbXNnX2FyZWFfbXNndW5yZWFkX2RhdGU7MzEbXBtbMG0gG1sxOzMwbbMbWzBtDQobWzUwQxtbMTszMG2zG1swbSAgICBQZW5kaW5nIE1lc3NhZ2VzOiAbWzMxbRtYbXNnX2FyZWFfcGVuZGluZzstNRtcG1szN20NCiAbWzE7MzJtMyAbWzM3bU9sZGVzdCBNZXNzYWdlIBtbMzRtG1htc2dfYXJlYV9tc2dvbGRlc3RfZGF0ZTszMRtcG1szN20gG1sxOzMwbbMbWzBtDQogG1sxOzMybTQgG1szN21MYXRlc3QgTWVzc2FnZSAbWzM0bRtYbXNnX2FyZWFfbXNnbmV3ZXN0X2RhdGU7MzEbXBtbMzdtIBtbMTszMG2zG1swbSAgICAgICAgICAbWzMybTkbWzBtIE5ldyBTY2FuOiAgIBtbMTszMm0bWG1zZ19hcmVhX25ld3NjYW47LTMbXBtbMG0NChtbNTBDG1sxOzMwbcEbWzBtDQogG1szMm01G1sxbSAbWzBtV3JpdGUgTmV3IE1lc3NhZ2UgICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzQ0bdXNzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3NzbgbWzBtDQogG1szMm02G1sxbSAbWzBtU2VhcmNoIGZvciBNZXNzYWdlICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzQ0bbMbWzMzbSCvIE1lc3NhZ2UgUmVhZGluZyBOYXZpZ2F0aW9uIK4gG1sxOzM3bbMbWzBtDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7NDRtsyAbWzMybRgbWzM2bSBTY3JvbGwgVVAgG1swOzQ0bSAgG1sxbbMbWzA7NDRtIBtbMTszMm0ZG1szNm0gU2Nyb2xsIERPV04bWzA7NDRtIBtbMTszM20gG1szN22zG1swbQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzQ0bbMgG1szMm0xG1szNm0gUHJldiBUbyBNZSAgG1sxOzMzbbMbWzA7NDRtIBtbMTszMm0yG1szNm0gTmV4dCBUbyBNZSAgG1szM20gG1szN22zG1swbQ0KIBtbMzJtOBtbMzdtIEFyZWEgU3VtbWFyeSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTs0NG2zG1szM20gG1szMm00IBtbMzZtUHJldmlvdXMgICAgG1sxOzMzbbMbWzA7NDRtIBtbMzJtMxtbMTszNm0gG1swOzM2OzQ0bVByZXYgVGhyZWFkG1szN20gG1sxOzMzbSAbWzM3bbMbWzBtDQogG1sxOzMybTAbWzA7MzJtIBtbMTszN21BcmVhIERlc2NyaXB0aW9uICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7NDRtsxtbMzNtIBtbMzJtNhtbMzZtIE5leHQbWzFtIBtbMDs0NG0gICAgICAgG1sxbbMbWzA7NDRtIBtbMzJtNxtbMzdtIBtbMzZtTmV4dCBUaHJlYWQbWzM3bSAbWzE7MzNtIBtbMzdtsxtbMG0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTs0NG2zG1swOzQ0bSAbWzMybTUbWzM3bSAbWzM2bVdyaXRlIE5ldxtbMzdtICAgG1sxbbMbWzA7NDRtIBtbMzJtOBtbMzdtIBtbMzZtUmVwbHkbWzM3bSAgICAgICAbWzE7MzNtIBtbMzdtsxtbMG0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTs0NG2zG1swOzQ0bSAbWzMybSMbWzM3bSAbWzM2bU1zZyBBdHRycyAgG1szN20gG1sxbbMbWzA7NDRtIBtbMTszMm0wG1szN20gG1sxOzM2bVJldHVybiBoZXJlG1szN20gG1sxOzMzbSAbWzM3bbMbWzBtDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7NDRt1M3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3NvhtbMG0NCg==","isPublic":0,"isAccessible":1,"type":"m","key":[null,null,null,null,null,null,null,null,null,null],"date":"2022-04-29T00:00:00.000Z"}
|
1
text/tex/199a
Normal file
1
text/tex/199a
Normal file
@ -0,0 +1 @@
|
||||
{"version":1,"frame":199,"index":"a","owner":1,"cost":0,"content":"DQogG1sxOzM2bURhdGU6IBtbMzdtQERBVEU6NjBADQogICAbWzE7MzZtVG86IBtbMzdtQFRPOjYwQA0KIBtbMTszNm1Gcm9tOiAbWzM3bUBGUk9NOjYwQA0KIBtbMTszNm1TdWJqOiAbWzM3bUBTVUJKRUNUOjYwQA0KIBtbMTszMG3ExMTExMQbWzBtxMTExMTEG1sxOzMwbcTExMTExBtbMG3ExMTExMQbWzE7MzBtxMTExMTEG1swbcTExMTExBtbMTszMG3ExMTExMQbWzBtxMTExMTEG1sxOzMwbcTExMTExBtbMG3ExMTExMQbWzE7MzBtxMTExMTEG1swbQ0K","isPublic":0,"isAccessible":1,"type":"m","key":[null,null,null,null,null,null,null,null,null,null],"date":"2022-04-29T00:00:00.000Z"}
|
BIN
text/tex/1a.ans
Normal file
BIN
text/tex/1a.ans
Normal file
Binary file not shown.
17
text/tex/980a.ans
Normal file
17
text/tex/980a.ans
Normal file
@ -0,0 +1,17 @@
|
||||
[0m [31mÚÄ[1mÜ[0;32mÚÄ[1mÜ[0;34mÚÄ[1mÜ[0;36m [1;33mß[0m [30;47mÚÂÜÚÄÜÚ Ü [37;40m
|
||||
[1mÚ ¿Â¿³Ú¿Ú¿Ú¿¿[0m [1;31m³[0;31mßÛ[1;32m³[0;36m [32mÛ[1;34m.\[0;34mÜ[33mÞ[1mÛ[0m [30;47m ³ Ã_ÜÚßÜ [37;40m
|
||||
³³³ÃÙ³³ ³³³ ³ÃÙ [1;30mÄÄÄÄÄÄÄÄÄÄÄÄ[0;30;47mÜÜÜÜÜÜÜÜÜÜ[37;40m
|
||||
[1;30mÀÁÙÁÙÀÀÙÀÙÀ ÙÁÙ[0m [1;30mnode:[0m [1;32mXnodeid;-11\[34m
|
||||
[0m [1;30mdate:[0;32m [1mXDATE:%Y-%b-%d;-11\[0m
|
||||
[1;30mtime: [32mXTIME;-11\[0m
|
||||
|
||||
[1mWelcome, you have connected to [31mA[32mN[34mS[33mI[0;30;47mtex[1;37;40m a BBS that is based on the[0m
|
||||
[1m1980's Videotex service, but using ANSI.[0m
|
||||
|
||||
[1mDepending on which country you live in the Videotex service was called[0m
|
||||
[1mViatel (AU), Prestel (UK), Minitex (FR), Telidon (CA), Ibertex (SP), etc[0m
|
||||
[1m
|
||||
[0m [1mIf you got here by mistake, you should disconnect now, otherwise, you can[0m
|
||||
[1m press[0m [1;5;32m0[0;1m to get to the login screen.[0m
|
||||
[1m
|
||||
[0m
|
7
text/tex/983a.ans
Normal file
7
text/tex/983a.ans
Normal file
@ -0,0 +1,7 @@
|
||||
[0m[31mレト[1mワ[0;32mレト[1mワ[0;34mレト[1mワ[0;36m [1;33m゚[0m [30;47mレツワレトワレ ワ [37;40m
|
||||
[1;31mウ[0;31m゚ロ[1;32mウ[0;36m [32mロ[1;34m.\[0;34mワ[33m゙[1mロ[0m [30;47m ウ テ_ワレ゚ワ [37;40m
|
||||
[1;30mトトトトトトトトトトトト[0;30;47mワワワワワワワワワワ[37;40m
|
||||
|
||||
[1mLogin [31mFAILED[37m.[0m
|
||||
|
||||
[1mTo try again, please press [32m0[0m
|
21
text/tex/98a.ans
Normal file
21
text/tex/98a.ans
Normal file
@ -0,0 +1,21 @@
|
||||
[0m [31mÚÄ[1mÜ[0;32mÚÄ[1mÜ[0;34mÚÄ[1mÜ[0;36m [1;33mß[0m [30;47mÚÂÜÚÄÜÚ Ü [37;40m
|
||||
[1;31m³[0;31mßÛ[1;32m³[0;36m [32mÛ[1;34m.\[0;34mÜ[33mÞ[1mÛ[0m [30;47m ³ Ã_ÜÚßÜ [37;40m
|
||||
[1;30mÄÄÄÄÄÄÄÄÄÄÄÄ[0;30;47mÜÜÜÜÜÜÜÜÜÜ[37;40m
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[1m. . [30m³[0m [1;30m³[0m
|
||||
[1mÚ¿¿Â¿Ú¿ ¿Ú¿[0m [1;30m³[0m [1;31mUSER:[0m [1m_USER;25t;ù\[0m [1;30m³[0m
|
||||
À¿³³³³³ ³³³ [1;30m³[0m [1;30m³[0m
|
||||
[1;30mÀÙÙÁ´ÙÙ[37m [30mÙÙÙ[37m [30m³[0m [1;31mPASS:[0m [1m_PASS;40p;ù\[0m [1;30m³[0m
|
||||
[1;30mÄÙ[0m [1;30m³[0m [1;30m³[0m
|
||||
|
||||
|
||||
[1;30mTo register an account enter[0m [1mNEW[0m [1;30mfor the user name[0m
|
||||
[1;30mUse[0m [1;32m**[0m [1;30mto clear your input[0m [1;32m*00[0m [1;30mto start again[0m
|
20
text/tex/98b.ans
Normal file
20
text/tex/98b.ans
Normal file
@ -0,0 +1,20 @@
|
||||
[0m [31mÚÄ[1mÜ[0;32mÚÄ[1mÜ[0;34mÚÄ[1mÜ[0;36m [1;33mß[0m [30;47mÚÂÜÚÄÜÚ Ü [37;40m
|
||||
[1mÚ ¿Â¿³Ú¿Ú¿Ú¿¿[0m [1;31m³[0;31mßÛ[1;32m³[0;36m [32mÛ[1;34m.\[0;34mÜ[33mÞ[1mÛ[0m [30;47m ³ Ã_ÜÚßÜ [37;40m
|
||||
³³³ÃÙ³³ ³³³ ³ÃÙ [1;30mÄÄÄÄÄÄÄÄÄÄÄÄ[0;30;47mÜÜÜÜÜÜÜÜÜÜ[37;40m
|
||||
[1;30mÀÁÙÁÙÀÀÙÀÙÀ ÙÁÙ[0m [1;30mnode:[0m [1;32mXnodeid;-11\[0m
|
||||
[1;30mdate: [1;32mXDATE:%Y-%b-%d;-11\[0m
|
||||
[1;30mtime: [1;32mXTIME;-11\[0m
|
||||
|
||||
Hi[1m XREALNAME;20\
|
||||
[0m
|
||||
Welcome to[1;30m [31mA[32mN[34mS[33mI[0;30;47mtex[37;40m, a BBS inspired by Videotex/Viewdata of the 1980's and
|
||||
1990's.
|
||||
|
||||
This BBS interface is build on top of Synchronet BBS, and might be something
|
||||
different to what you are used to with other Synchronet BBS'.
|
||||
[1;31m
|
||||
[0m Navigation is via pages, which can be accessed via [1;32m*[0;32mpagenum[1m#[0m. Information is
|
||||
available via page [1;32m*516#[0m, which will help you get started if you need it.
|
||||
To get to the home page at any time, use [1;32m*0#[0m.
|
||||
|
||||
[1mPress[0m [1;5;32m0[0;1;33m [37mMain Menu[0m
|
21
text/tex/999a.ans
Normal file
21
text/tex/999a.ans
Normal file
@ -0,0 +1,21 @@
|
||||
[0m [1mĂ-ÂżÚżĂ- ڿ¿¿¿[0m [1mAB[33mCD[37mEF[33mGH[37mIJ[33mKL[37mMN[33mOP[37mQR[33mST[37mUV[33mWX[37mYZ[0m
|
||||
ł ĂŮŔżł łłÂ´łłĂŮ [1;36mab[37mcd[36mef[37mgh[36mij[37mkl[36mmn[37mop[36mqr[37mst[36muv[37mwx[36myz[0m
|
||||
[1;31mŔŮÁŮŔŮŔŮ ĂŮÁÁÁ´ÁŮ[0m [1m!@[35m#$[37m%^[35m&*[37m()[35m_+[37m{}[35m|\[37m:;[35m"'[37m<>[35m?,[37m./[35m~[0m
|
||||
[1;30mŰß[0mßß[1mßß[0mßß[1;30mßß[31mŔ[0mß[1mßß[0mß[1;31mŮ[30mßß[0mßß[1mßß[0mßß[1;30mßß[0mßß[1mßß[0mßß[1;30mßß[0mßß[1mßß[0mßß[1;30mßß[0mßß[1mßß[0mßß[1;30mßß[0mßß[1mßß[0mßß[1;30mßß[0mßß[1mßß[0mßß[1;30mßß[0mßß[1mßß[0mßß[1;30mßß[0mßß[1mßß[0mŰ
|
||||
Ű[1;30m [0;30mBLA[37m [34mBLU[37m [32mGRE[37m [36mCYA[37m [31mRED[37m [35mMAG[37m [33mYEL[37m WHI [1;30mBLA[0m [1;34mBLU[0m [1;32mGRE[0m [1;36mCYA[0m [1;31mRED[0m [1;35mMAG[0m [1;33mYEL[0m [1mWHI[30mł[37m0[0m Ú[1;30mż[0mŔ[1;30mŮ[0mÄ[1;30mł[0mĂ[1;30m´[0mÁ[1;30mÂŢ[0m
|
||||
[1mŰ[30m [0;30;44mBLA[37m [34mBLU[37m [32mGRE[37m [36mCYA[37m [31mRED[37m [35mMAG[37m [33mYEL[37m WHI [1;30mBLA[0;44m [1;34mBLU[0;44m [1;32mGRE[0;44m [1;36mCYA[0;44m [1;31mRED[0;44m [1;35mMAG[0;44m [1;33mYEL[0;44m [1mWHI[30;40mł[37m1[0m É[1;30m»[0mČ[1;30mĽ[0mÍ[1;30mş[0mĚ[1;30mą[0mĘ[1;30mË[0mŢ
|
||||
Ű[1;30m [0;30;42mBLA[37m [34mBLU[37m [32mGRE[37m [36mCYA[37m [31mRED[37m [35mMAG[37m [33mYEL[37m WHI [1;30mBLA[0;42m [1;34mBLU[0;42m [1;32mGRE[0;42m [1;36mCYA[0;42m [1;31mRED[0;42m [1;35mMAG[0;42m [1;33mYEL[0;42m [1mWHI[30;40mł[31m2[0m Ő[1;30m¸[0mÔ[1;30mľ[0mÍ[1;30mł[0mĆ[1;30mµ[0mĎ[1;30mŃ[37mŢ[0m
|
||||
[1;30mŰ [0;30;46mBLA[37m [34mBLU[37m [32mGRE[37m [36mCYA[37m [31mRED[37m [35mMAG[37m [33mYEL[37m WHI [1;30mBLA[0;46m [1;34mBLU[0;46m [1;32mGRE[0;46m [1;36mCYA[0;46m [1;31mRED[0;46m [1;35mMAG[0;46m [1;33mYEL[0;46m [1mWHI[30;40mł[31m3[0m Ö[1;30m·[0mÓ[1;30m˝[0mÄ[1;30mş[0mÇ[1;30m¶[0mĐ[1;30mŇ[0mŢ
|
||||
Ű[1;30m [0;30;41mBLA[37m [34mBLU[37m [32mGRE[37m [36mCYA[37m [31mRED[37m [35mMAG[37m [33mYEL[37m WHI [1;30mBLA[0;41m [1;34mBLU[0;41m [1;32mGRE[0;41m [1;36mCYA[0;41m [1;31mRED[0;41m [1;35mMAG[0;41m [1;33mYEL[0;41m [1mWHI[30;40mł[37m4[0m Ĺ[1;30mÎ[0mŘ[1;30m×[0mč[1;30mč[0m›[1;30mś[0m™[1;30mďŢ[0m
|
||||
[1mŰ[30m [0;30;45mBLA[37m [34mBLU[37m [32mGRE[37m [36mCYA[37m [31mRED[37m [35mMAG[37m [33mYEL[37m WHI [1;30mBLA[0;45m [1;34mBLU[0;45m [1;32mGRE[0;45m [1;36mCYA[0;45m [1;31mRED[0;45m [1;35mMAG[0;45m [1;33mYEL[0;45m [1mWHI[30;40mł[37m5[0m °[1;30m±[0m˛[1;30mŰ[0mß[1;30mÜ[0mÝ[1;30mŢ[0mţ[1;30mú[0mŢ
|
||||
Ű[1;30m [0;30;43mBLA[37m [34mBLU[37m [32mGRE[37m [36mCYA[37m [31mRED[37m [35mMAG[37m [33mYEL[37m WHI [1;30mBLA[0;43m [1;34mBLU[0;43m [1;32mGRE[0;43m [1;36mCYA[0;43m [1;31mRED[0;43m [1;35mMAG[0;43m [1;33mYEL[0;43m [1mWHI[30;40mł[31m6[0m [1;30m[0m[1;30m[0m[1;30m[0mđ[1;30m[0m[1;30m [37mŢ[0m
|
||||
[1;30mŰ [0;30;47mBLA[37m [34mBLU[37m [32mGRE[37m [36mCYA[37m [31mRED[37m [35mMAG[37m [33mYEL[37m WHI [1;30mBLA[0;47m [1;34mBLU[0;47m [1;32mGRE[0;47m [1;36mCYA[0;47m [1;31mRED[0;47m [1;35mMAG[0;47m [1;33mYEL[0;47m [1mWHI[30;40mł[31m7[0m [1;30m[0m[1;30m[0m[1;30m[0m[1;30m[0m[1;30m[0mŢ
|
||||
Ű[1;30m [0;5;30mBLA[37m [34mBLU[37m [32mGRE[37m [36mCYA[37m [31mRED[37m [35mMAG[37m [33mYEL[37m WHI [1;30mBLA[0;5m [1;34mBLU[0;5m [1;32mGRE[0;5m [1;36mCYA[0;5m [1;31mRED[0;5m [1;35mMAG[0;5m [1;33mYEL[0;5m [1mWHI[0;1;30mł[37m8[0m [1;30m®[0mŻň[1;30mó[0m©[1;30mŞ[0mý[1;30mö[0m«[1;30m¬Ţ[0m
|
||||
[1mŰ[30m [0;5;30;44mBLA[37m [34mBLU[37m [32mGRE[37m [36mCYA[37m [31mRED[37m [35mMAG[37m [33mYEL[37m WHI [1;30mBLA[0;5;44m [1;34mBLU[0;5;44m [1;32mGRE[0;5;44m [1;36mCYA[0;5;44m [1;31mRED[0;5;44m [1;35mMAG[0;5;44m [1;33mYEL[0;5;44m [1mWHI[0;1;30mł[37m9[0m [1;30mă[0mńô[1;30mő[0mę[1;30mť[0mä[1;30mř[0mű[1;30mü[0mŢ
|
||||
Ű[1;30m [0;5;30;42mBLA[37m [34mBLU[37m [32mGRE[37m [36mCYA[37m [31mRED[37m [35mMAG[37m [33mYEL[37m WHI [1;30mBLA[0;5;42m [1;34mBLU[0;5;42m [1;32mGRE[0;5;42m [1;36mCYA[0;5;42m [1;31mRED[0;5;42m [1;35mMAG[0;5;42m [1;33mYEL[0;5;42m [1mWHI[0;1;30mł[31mA[0m [1;30mŕ[0máâ[1;30mĺ[0mć[1;30mç[0më[1;30mě[0mí[1;30mî[37mŢ[0m
|
||||
[1;30mŰ [0;5;30;46mBLA[37m [34mBLU[37m [32mGRE[37m [36mCYA[37m [31mRED[37m [35mMAG[37m [33mYEL[37m WHI [1;30mBLA[0;5;46m [1;34mBLU[0;5;46m [1;32mGRE[0;5;46m [1;36mCYA[0;5;46m [1;31mRED[0;5;46m [1;35mMAG[0;5;46m [1;33mYEL[0;5;46m [1mWHI[0;1;30mł[31mB[0m [1;30m€[0m‡Ą[1;30m¤[0m<30>[1;30mź[0m÷[1;30mů[0m[1;30m¨[0mŢ
|
||||
Ű[1;30m [0;5;30;41mBLA[37m [34mBLU[37m [32mGRE[37m [36mCYA[37m [31mRED[37m [35mMAG[37m [33mYEL[37m WHI [1;30mBLA[0;5;41m [1;34mBLU[0;5;41m [1;32mGRE[0;5;41m [1;36mCYA[0;5;41m [1;31mRED[0;5;41m [1;35mMAG[0;5;41m [1;33mYEL[0;5;41m [1mWHI[0;1;30mł[37mC[0m [1;30m<30>[0m„…[1;30m [0m¦[1;30m†[0mŽ[1;30mŹ[0m‘[1;30m’Ţ[0m
|
||||
[1mŰ[30m [0;5;30;45mBLA[37m [34mBLU[37m [32mGRE[37m [36mCYA[37m [31mRED[37m [35mMAG[37m [33mYEL[37m WHI [1;30mBLA[0;5;45m [1;34mBLU[0;5;45m [1;32mGRE[0;5;45m [1;36mCYA[0;5;45m [1;31mRED[0;5;45m [1;35mMAG[0;5;45m [1;33mYEL[0;5;45m [1mWHI[0;1;30mł[37mD[0m [1;30m<30>[0m‰Š[1;30m‚[0m<30>[1;30mŚ[0m‹[1;30mŤ[0mˇ[1;30mž[0mŢ
|
||||
Ű[1;30m [0;5;30;43mBLA[37m [34mBLU[37m [32mGRE[37m [36mCYA[37m [31mRED[37m [35mMAG[37m [33mYEL[37m WHI [1;30mBLA[0;5;43m [1;34mBLU[0;5;43m [1;32mGRE[0;5;43m [1;36mCYA[0;5;43m [1;31mRED[0;5;43m [1;35mMAG[0;5;43m [1;33mYEL[0;5;43m [1mWHI[0;1;30mł[31mE[0m [1;30m“[0m”•[1;30m˘[0m§[1;30m–[0m<30>[1;30m—[0mŁ[1;30mš[37mŢ[0m
|
||||
[1;30mŰ [0;5;30;47mBLA[37m [34mBLU[37m [32mGRE[37m [36mCYA[37m [31mRED[37m [35mMAG[37m [33mYEL[37m WHI [1;30mBLA[0;5;47m [1;34mBLU[0;5;47m [1;32mGRE[0;5;47m [1;36mCYA[0;5;47m [1;31mRED[0;5;47m [1;35mMAG[0;5;47m [1;33mYEL[0;5;47m [1mWHI[0;1;30mł[31mF[0m [1;30m/[0m\([1;30m)[0m{[1;30m}[0m[[1;30m][0m`[1;30m'[0mŢ
|
||||
ŰÜ[1;30mÜÜ[0mÜÜ[1mÜÜ[0mÜÜ[1;30mÜÜ[0mÜÜ[1mÜÜ[0mÜÜ[1;30mÜÜ[0mÜÜ[1mÜÜ[0mÜÜ[1;30mÜÜ[0mÜÜ[1mÜÜ[0mÜÜ[1;30mÜÜ[0mÜÜ[1mÜÜ[0mÜÜ[1;30mÜÜ[0mÜÜ[1mÜÜ[0mÜÜ[1;30mÜÜ[0mÜÜ[1mÜÜ[0mÜÜ[1;30mÜÜ[0mÜÜ[1mÜÜ[0mÜÜ[1;30mÜÜ[0mÜÜ[1mÜÜ[0mÜÜ[1;30mÜÜ[0mÜÜ[1mŰ[0m
|
21
text/tex/999b.ans
Normal file
21
text/tex/999b.ans
Normal file
@ -0,0 +1,21 @@
|
||||
[0m [1mĂ-ÂżÚżĂ- ڿ¿¿¿[0m [1mAB[33mCD[37mEF[33mGH[37mIJ[33mKL[37mMN[33mOP[37mQR[33mST[37mUV[33mWX[37mYZ[0m
|
||||
ł ĂŮŔżł łłÂ´łłĂŮ [1;36mab[37mcd[36mef[37mgh[36mij[37mkl[36mmn[37mop[36mqr[37mst[36muv[37mwx[36myz[0m
|
||||
[1;31mŔŮÁŮŔŮŔŮ ĂŮÁÁÁ´ÁŮ[0m [1m!@[35m#$[37m%^[35m&*[37m()[35m_+[37m{}[35m|\[37m:;[35m"'[37m<>[35m?,[37m./[35m~[0m
|
||||
[1;30mŰß[0mßß[1mßß[0mßß[1;30mßß[31mŔ[0mß[1mßß[0mß[1;31mŮ[30mßß[0mßß[1mßß[0mßß[1;30mßß[0mßß[1mßß[0mßß[1;30mßß[0mßß[1mßß[0mßß[1;30mßß[0mßß[1mßß[0mßß[1;30mßß[0mßß[1mßß[0mßß[1;30mßß[0mßß[1mßß[0mßß[1;30mßß[0mßß[1mßß[0mßß[1;30mßß[0mßß[1mßß[0mŰ
|
||||
[1;30mŰ[0;30m BLA[5m BLA [0;30;41mRED [5mRED[0;30;41m [42mGRE [5mGRE[0;30;42m [43mYEL [5mYEL[0;30;43m [44mBLU [5mBLU[0;30;44m [45mMAG [5mMAG[0;30;45m [46mCYA [5mCYA[0;30;46m [47mWHI [5mWHI[0;1;30mł[37m0[0m Ú[1;30mż[0mŔ[1;30mŮ[0mÄ[1;30mł[0mĂ[1;30m´[0mÁ[1;30mÂŢ[0m
|
||||
[1mŰ[0m [1;30mBLA[0m [1;5;30mBLA[0m [1;30;41mRED [5mRED[0;1;30;41m [42mGRE [5mGRE[0;1;30;42m [43mYEL [5mYEL[0;1;30;43m [44mBLU [5mBLU[0;1;30;44m [45mMAG [5mMAG[0;1;30;45m [46mCYA [5mCYA[0;1;30;46m [47mWHI[0;30;47m [1;5mWHI[0;1;30mł[37m1[0m É[1;30m»[0mČ[1;30mĽ[0mÍ[1;30mş[0mĚ[1;30mą[0mĘ[1;30mË[0mŢ
|
||||
Ű [31mBLA[37m [5;31mBLA[0m [31;41mRED [5mRED[0;31;41m [42mGRE [5mGRE[0;31;42m [43mYEL [5mYEL[0;31;43m [44mBLU [5mBLU[0;31;44m [45mMAG [5mMAG[0;31;45m [46mCYA [5mCYA[0;31;46m [47mWHI[30m [5;31mWHI[0;1;30mł[31m2[0m Ő[1;30m¸[0mÔ[1;30mľ[0mÍ[1;30mł[0mĆ[1;30mµ[0mĎ[1;30mŃ[37mŢ[0m
|
||||
[1;30mŰ[0m [1;31mBLA[0m [1;5;31mBLA[0m [1;31;41mRED [5mRED[0;1;31;41m [42mGRE [5mGRE[0;1;31;42m [43mYEL [5mYEL[0;1;31;43m [44mBLU [5mBLU[0;1;31;44m [45mMAG [5mMAG[0;1;31;45m [46mCYA [5mCYA[0;1;31;46m [47mWHI[0;30;47m [1;5;31mWHI[0;1;30mł[31m3[0m Ö[1;30m·[0mÓ[1;30m˝[0mÄ[1;30mş[0mÇ[1;30m¶[0mĐ[1;30mŇ[0mŢ
|
||||
Ű [32mBLA[37m [5;32mBLA[0m [32;41mRED [5mRED[0;32;41m [42mGRE [5mGRE[0;32;42m [43mYEL [5mYEL[0;32;43m [44mBLU [5mBLU[0;32;44m [45mMAG [5mMAG[0;32;45m [46mCYA [5mCYA[0;32;46m [47mWHI[30m [5;32mWHI[0;1;30mł[37m4[0m Ĺ[1;30mÎ[0mŘ[1;30m×[0mč[1;30mč[0m›[1;30mś[0m™[1;30mďŢ[0m
|
||||
[1mŰ[0m [1;32mBLA[0m [1;5;32mBLA[0m [1;32;41mRED [5mRED[0;1;32;41m [42mGRE [5mGRE[0;1;32;42m [43mYEL [5mYEL[0;1;32;43m [44mBLU [5mBLU[0;1;32;44m [45mMAG [5mMAG[0;1;32;45m [46mCYA [5mCYA[0;1;32;46m [47mWHI[0;30;47m [1;5;32mWHI[0;1;30mł[37m5[0m °[1;30m±[0m˛[1;30mŰ[0mß[1;30mÜ[0mÝ[1;30mŢ[0mţ[1;30mú[0mŢ
|
||||
Ű [33mBLA[37m [5;33mBLA[0m [33;41mRED [5mRED[0;33;41m [42mGRE [5mGRE[0;33;42m [43mYEL [5mYEL[0;33;43m [44mBLU [5mBLU[0;33;44m [45mMAG [5mMAG[0;33;45m [46mCYA [5mCYA[0;33;46m [47mWHI[30m [5;33mWHI[0;1;30mł[31m6[0m [1;30m[0m[1;30m[0m[1;30m[0mđ[1;30m[0m[1;30m [37mŢ[0m
|
||||
[1;30mŰ[0m [1;33mBLA[0m [1;5;33mBLA[0m [1;33;41mRED [5mRED[0;1;33;41m [42mGRE [5mGRE[0;1;33;42m [43mYEL [5mYEL[0;1;33;43m [44mBLU [5mBLU[0;1;33;44m [45mMAG [5mMAG[0;1;33;45m [46mCYA [5mCYA[0;1;33;46m [47mWHI[0;30;47m [1;5;33mWHI[0;1;30mł[31m7[0m [1;30m[0m[1;30m[0m[1;30m[0m[1;30m[0m[1;30m[0mŢ
|
||||
Ű [34mBLA[37m [5;34mBLA[0m [34;41mRED [5mRED[0;34;41m [42mGRE [5mGRE[0;34;42m [43mYEL [5mYEL[0;34;43m [44mBLU [5mBLU[0;34;44m [45mMAG [5mMAG[0;34;45m [46mCYA [5mCYA[0;34;46m [47mWHI[30m [5;34mWHI[0;1;30mł[37m8[0m [1;30m®[0mŻň[1;30mó[0m©[1;30mŞ[0mý[1;30mö[0m«[1;30m¬Ţ[0m
|
||||
[1mŰ[0m [1;34mBLA[0m [1;5;34mBLA[0m [1;34;41mRED [5mRED[0;1;34;41m [42mGRE [5mGRE[0;1;34;42m [43mYEL [5mYEL[0;1;34;43m [44mBLU [5mBLU[0;1;34;44m [45mMAG [5mMAG[0;1;34;45m [46mCYA [5mCYA[0;1;34;46m [47mWHI[0;30;47m [1;5;34mWHI[0;1;30mł[37m9[0m [1;30mă[0mńô[1;30mő[0mę[1;30mť[0mä[1;30mř[0mű[1;30mü[0mŢ
|
||||
Ű [35mBLA[37m [5;35mBLA[0m [35;41mRED [5mRED[0;35;41m [42mGRE [5mGRE[0;35;42m [43mYEL [5mYEL[0;35;43m [44mBLU [5mBLU[0;35;44m [45mMAG [5mMAG[0;35;45m [46mCYA [5mCYA[0;35;46m [47mWHI[30m [5;35mWHI[0;1;30mł[31mA[0m [1;30mŕ[0máâ[1;30mĺ[0mć[1;30mç[0më[1;30mě[0mí[1;30mî[37mŢ[0m
|
||||
[1;30mŰ[0m [1;35mBLA[0m [1;5;35mBLA[0m [1;35;41mRED [5mRED[0;1;35;41m [42mGRE [5mGRE[0;1;35;42m [43mYEL [5mYEL[0;1;35;43m [44mBLU [5mBLU[0;1;35;44m [45mMAG [5mMAG[0;1;35;45m [46mCYA [5mCYA[0;1;35;46m [47mWHI[0;30;47m [1;5;35mWHI[0;1;30mł[31mB[0m [1;30m€[0m‡Ą[1;30m¤[0m<30>[1;30mź[0m÷[1;30mů[0m[1;30m¨[0mŢ
|
||||
Ű [36mBLA[37m [5;36mBLA[0m [36;41mRED [5mRED[0;36;41m [42mGRE [5mGRE[0;36;42m [43mYEL [5mYEL[0;36;43m [44mBLU [5mBLU[0;36;44m [45mMAG [5mMAG[0;36;45m [46mCYA [5mCYA[0;36;46m [47mWHI[30m [5;36mWHI[0;1;30mł[37mC[0m [1;30m<30>[0m„…[1;30m [0m¦[1;30m†[0mŽ[1;30mŹ[0m‘[1;30m’Ţ[0m
|
||||
[1mŰ[0m [1;36mBLA[0m [1;5;36mBLA[0m [1;36;41mRED [5mRED[0;1;36;41m [42mGRE [5mGRE[0;1;36;42m [43mYEL [5mYEL[0;1;36;43m [44mBLU [5mBLU[0;1;36;44m [45mMAG [5mMAG[0;1;36;45m [46mCYA [5mCYA[0;1;36;46m [47mWHI[0;30;47m [1;5;36mWHI[0;1;30mł[37mD[0m [1;30m<30>[0m‰Š[1;30m‚[0m<30>[1;30mŚ[0m‹[1;30mŤ[0mˇ[1;30mž[0mŢ
|
||||
Ű BLA [5mBLA[0m [41mRED [5mRED[0;41m [42mGRE [5mGRE[0;42m [43mYEL [5mYEL[0;43m [44mBLU [5mBLU[0;44m [45mMAG [5mMAG[0;45m [46mCYA [5mCYA[0;46m [47mWHI[30m [5;37mWHI[0;1;30mł[31mE[0m [1;30m“[0m”•[1;30m˘[0m§[1;30m–[0m<30>[1;30m—[0mŁ[1;30mš[37mŢ[0m
|
||||
[1;30mŰ[0m [1mBLA[0m [1;5mBLA[0m [1;41mRED [5mRED[0;1;41m [42mGRE [5mGRE[0;1;42m [43mYEL [5mYEL[0;1;43m [44mBLU [5mBLU[0;1;44m [45mMAG [5mMAG[0;1;45m [46mCYA [5mCYA[0;1;46m [47mWHI[0;30;47m [1;5;37mWHI[0;1;30mł[31mF[0m [1;30m/[0m\([1;30m)[0m{[1;30m}[0m[[1;30m][0m`[1;30m'[0mŢ
|
||||
ŰÜ[1;30mÜÜ[0mÜÜ[1mÜÜ[0mÜÜ[1;30mÜÜ[0mÜÜ[1mÜÜ[0mÜÜ[1;30mÜÜ[0mÜÜ[1mÜÜ[0mÜÜ[1;30mÜÜ[0mÜÜ[1mÜÜ[0mÜÜ[1;30mÜÜ[0mÜÜ[1mÜÜ[0mÜÜ[1;30mÜÜ[0mÜÜ[1mÜÜ[0mÜÜ[1;30mÜÜ[0mÜÜ[1mÜÜ[0mÜÜ[1;30mÜÜ[0mÜÜ[1mÜÜ[0mÜÜ[1;30mÜÜ[0mÜÜ[1mÜÜ[0mÜÜ[1;30mÜÜ[0mÜÜ[1mŰ[0m
|
21
text/tex/99a.ans
Normal file
21
text/tex/99a.ans
Normal file
@ -0,0 +1,21 @@
|
||||
[0m [1mÚ¿[0m [31mÚÄ[1mÜ[0;32mÚÄ[1mÜ[0;34mÚÄ[1mÜ[0;36m [1;33mß[0m [30;47mÚÂÜÚÄÜÚ Ü [37;40m
|
||||
[1m¿ڿڿڴÿ¿Ú¿[0m [1;31m³[0;31mßÛ[1;32m³[0;36m [32mÛ[1;34m.\[0;34mÜ[33mÞ[1mÛ[0m [30;47m ³ Ã_ÜÚßÜ [37;40m
|
||||
³³³³³³³³³³³³ÃÙ [1;30mÄÄÄÄÄÄÄÄÄÄÄÄ[0;30;47mÜÜÜÜÜÜÜÜÜÜ[37;40m
|
||||
[1;30mÁ´ÀÙÀÙÀÙÀÙÀ´ÁÙ[0m [1;30mnode:[0m [1;32mXnodeid;-11\[0m
|
||||
[1;30mÄÙ[31m [30mÄÙ[0m [1;30mdate:[0m [1;32mXDATE:%Y-%b-%d;-11\[0m
|
||||
[1;30mtime:[0m [1;32mXTIME;-11\[0m
|
||||
|
||||
You are about to disconnect from [1;31mA[32mN[34mS[33mI[0;30;47mtex[37;40m [1mXREALNAME;20\[0m
|
||||
|
||||
If you wanted to stay online, you can press [1;32m*0#[0m to get back to the main menu
|
||||
|
||||
Otherwise press [1;5;32m#[0;1;33m [0mto disconnect now.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[1;5;32m*0#[0;1m to get back to the Main Menu[0m
|
8
text/tex/99b.ans
Normal file
8
text/tex/99b.ans
Normal file
@ -0,0 +1,8 @@
|
||||
[0m [1mÚ¿[0m [31mÚÄ[1mÜ[0;32mÚÄ[1mÜ[0;34mÚÄ[1mÜ[0;36m [1;33mß[0m [30;47mÚÂÜÚÄÜÚ Ü [37;40m
|
||||
[1m¿ڿڿڴÿ¿Ú¿[0m [1;31m³[0;31mßÛ[1;32m³[0;36m [32mÛ[1;34m.\[0;34mÜ[33mÞ[1mÛ[0m [30;47m ³ Ã_ÜÚßÜ [37;40m
|
||||
³³³³³³³³³³³³ÃÙ [1;30mÄÄÄÄÄÄÄÄÄÄÄÄ[0;30;47mÜÜÜÜÜÜÜÜÜÜ[37;40m
|
||||
[1;30mÁ´ÀÙÀÙÀÙÀÙÀ´ÁÙ[0m [1;30mnode:[0m [1;32mXnodeid;-11\[0m [1;30mÄÙ[31m [30mÄÙ[0m [1;30mdate:[0m [1;32mXDATE:%Y-%b-%d;-11\[0m [1;30mtime:[0m [1;32mXTIME;-11\[0m
|
||||
|
||||
Thanks for visiting [1;31mA[32mN[34mS[33mI[0;30;47mtex[37;40m [1mXREALNAME;20\[0m
|
||||
|
||||
Hope to see you again...
|
1
text/vtx/1a.bin
Normal file
1
text/vtx/1a.bin
Normal file
@ -0,0 +1 @@
|
||||
7ccks3w{#35 ,l|l|,,7k#37k#55jjj 45j 55 kj/|5ho <=$,5uzzzpquzpu5 /./*,//upzpquzp5 Videotex *0_To get back here DATE........... NODE ........$0$0$0$0$0$0$0$0$0$0$0$0$0$0$0$0$0$0$0$ 1Fido Message Networks 8Navigating AnsiTEX 9About AnsiTEX *95_ Help *99_ Log off
|
1
text/vtx/980a.bin
Normal file
1
text/vtx/980a.bin
Normal file
@ -0,0 +1 @@
|
||||
,l|l|,,7k#37k#5 kj/|5ho <=$,5 /./*,//upzpquzp5 Videotex
Welcome Welcome, you have connected to ANSItex a BBS that is based on the 1980's Videotex service. Depending on which country you live in the Videotex service was called Viatel (AU), Prestel (UK), Minitel (FR), Telidon (CA), Ibertex (SP), etc If you got here by mistake, you should disconnect now, otherwise you can press0to get to the login screen. To login using SQRL, press1
|
1
text/vtx/981a.bin
Normal file
1
text/vtx/981a.bin
Normal file
@ -0,0 +1 @@
|
||||
,l|l|,,7k#37k#5 ........kj/|5ho <=$,5 /./*,//upzpquzp5 Videotex
Register ** to clear input *00to start again Use_to Enter !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Email :eeeeeeeeeeeeeeeeeeeeeeeee User ID :nnnnnnnnnnnnnnn Password :ppppppppppppppp Full Name:fffffffffffffffffffffffff Token :zzzzzz 000000000000000000000000000000000000 Registering and using this system, you agree to abide by the system rules. See*988_
|
1
text/vtx/981b.bin
Normal file
1
text/vtx/981b.bin
Normal file
@ -0,0 +1 @@
|
||||
,l|l|,,7k#37k#5 ........kj/|5ho <=$,5 /./*,//upzpquzp5 Videotex
Welcome Welcome toANSItex, a bulletin board system based on the 1980's Videotex service. Your usage here is governed by some rules, which you can see on page*988_ If you are not familiar with Videotex, you can find some information on page *516_ To get to the home page anytime, use *0_or use0now.
|
1
text/vtx/983a.bin
Normal file
1
text/vtx/983a.bin
Normal file
@ -0,0 +1 @@
|
||||
,l|l|,,7k#37k#5 ........kj/|5ho <=$,5 /./*,//upzpquzp5 Videotex Login
FAILED Press0to try again. If this keeps happening, you may like to request that your password is reset.
|
1
text/vtx/98a.bin
Normal file
1
text/vtx/98a.bin
Normal file
@ -0,0 +1 @@
|
||||
,l|l|,,7k#37k#5 ........kj/|5ho <=$,5 /./*,//upzpquzp5 Videotex
Sign In User............... Pass............... EnterNEWto register new account Press**to clear your input Press*00to start again
|
1
text/vtx/98b.bin
Normal file
1
text/vtx/98b.bin
Normal file
@ -0,0 +1 @@
|
||||
,l|l|,,7k#37k#5 ........kj/|5ho <=$,5 /./*,//upzpquzp5 Videotex
Sign In Welcome toANSItex. To get to the main menu, you can press*0_any time. Press0to continue
|
BIN
text/vtx/999a.bin
Normal file
BIN
text/vtx/999a.bin
Normal file
Binary file not shown.
BIN
text/vtx/999b.bin
Normal file
BIN
text/vtx/999b.bin
Normal file
Binary file not shown.
1
text/vtx/99a.bin
Normal file
1
text/vtx/99a.bin
Normal file
@ -0,0 +1 @@
|
||||
7+o/??/o ,l|l|,,7k#37k#55jj %5sz kj/|5ho <=$,5urzsqusz /./*,//upzpquzp5 Videotex You are about todisconnectfrom ANSItex. To head back to the main menu, please use*0_ Otherwise to disconnect press_
Thanks for calling
|
1
text/vtx/99b.bin
Normal file
1
text/vtx/99b.bin
Normal file
@ -0,0 +1 @@
|
||||
7+o/??/o ,l|l|,,7k#37k#55jj %5sz kj/|5ho <=$,5urzsqusz /./*,//upzpquzp5 Videotex
Thanks for calling Hope to see you again.
|
37
tools/clear_tags.js
Normal file
37
tools/clear_tags.js
Normal file
@ -0,0 +1,37 @@
|
||||
/** CLEAR ALL THE TAGS IN A MESSAGE BASE */
|
||||
|
||||
load('ansitex/load/msgbases.js');
|
||||
|
||||
if (argv.length !== 1) {
|
||||
writeln('ERROR: Need a msgbase page prefix');
|
||||
exit(1);
|
||||
|
||||
} else {
|
||||
writeln('Showing frames in:'+argv[0]);
|
||||
}
|
||||
|
||||
var ma = new MsgAreas();
|
||||
var area = ma.getArea(argv[0]);
|
||||
|
||||
writeln('Opening ['+argv[0]+'] - ('+area.code+')');
|
||||
|
||||
var msgbase = area.msgbase;
|
||||
msgbase.open();
|
||||
writeln('- First:'+msgbase.first_msg);
|
||||
writeln('- Last:'+msgbase.last_msg);
|
||||
|
||||
var msgs = msgbase.get_all_msg_headers(false,false) || [];
|
||||
|
||||
for (var x in msgs) {
|
||||
if (! msgs[x].tags)
|
||||
continue;
|
||||
|
||||
writeln('TAG was:'+msgs[x].tags);
|
||||
|
||||
msgs[x].tags = '';
|
||||
|
||||
if (! msgbase.put_msg_header(msgs[x].number,msgs[x]))
|
||||
writeln('ERROR:'+msgbase.error);
|
||||
}
|
||||
|
||||
msgbase.close();
|
243
tools/export.js
Normal file
243
tools/export.js
Normal file
@ -0,0 +1,243 @@
|
||||
load('smbdefs.js');
|
||||
|
||||
// ANSItex specific includes
|
||||
load('ansitex/load/defs.js');
|
||||
load('ansitex/load/funcs.js');
|
||||
|
||||
/**
|
||||
* Work out which key should be used for a page, or validate if a key used is valid
|
||||
*
|
||||
* @param page
|
||||
* @param signed
|
||||
* @returns {*}
|
||||
*/
|
||||
function getKey(page,signed) {
|
||||
log(LOG_DEBUG,'+ CHECKING key:'+signed+' for page: '+page);
|
||||
|
||||
var f = new File(file_cfgname(system.mods_dir,'ansitex/ctrl/videotex.ini'));
|
||||
var match = '';
|
||||
|
||||
if (f.open("r")) {
|
||||
var BreakException = {};
|
||||
|
||||
// Default Key
|
||||
var key = f.iniGetValue('prefix','key');
|
||||
|
||||
try {
|
||||
f.iniGetSections("prefix:").forEach(function (prefix) {
|
||||
var p = prefix.substr(7);
|
||||
var pk = f.iniGetValue(prefix, 'Key', '');
|
||||
var re = new RegExp('^' + p, 'g');
|
||||
|
||||
// If it was signed, is the key a value one
|
||||
if (signed && signed.length) {
|
||||
if (pk === signed) {
|
||||
//print('SIGNED key is valid:' + signed);
|
||||
key = signed;
|
||||
throw BreakException;
|
||||
}
|
||||
|
||||
// Which key should sign this page
|
||||
} else if (page.toString().match(re) && (p.length > match.length)) {
|
||||
match = p;
|
||||
key = pk;
|
||||
}
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
if (e !== BreakException) throw e;
|
||||
}
|
||||
}
|
||||
|
||||
f.close();
|
||||
|
||||
log(LOG_DEBUG,'- key:'+key);
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export message from message base
|
||||
*
|
||||
* @param msgbase
|
||||
*/
|
||||
function msgBaseExport(msgbase) {
|
||||
var ini = new File(msgbase.file+'.ini');
|
||||
var i=0;
|
||||
|
||||
if (ini.open("r")) {
|
||||
export_ptr=ini.iniGetValue('videotex','export_ptr',0);
|
||||
ini.close();
|
||||
|
||||
} else {
|
||||
log(LOG_ERROR,'! ERROR: Unable to open INI for ['+msgbase.file+']');
|
||||
}
|
||||
|
||||
// If pointer doesnt exist, reset it from the message base.
|
||||
if (export_ptr === undefined) {
|
||||
var f = new File(file_getcase(msgbase.file+'.sbl'));
|
||||
|
||||
if (f.open('rb')) {
|
||||
export_ptr = f.readBin(4);
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
|
||||
highest = export_ptr;
|
||||
var total_msgs = msgbase.total_msgs;
|
||||
|
||||
log(LOG_DEBUG,'| msgBaseExport: export_ptr='+export_ptr+'|last_msg='+msgbase.last_msg+'|total_msgs='+total_msgs);
|
||||
|
||||
if (msgbase.last_msg >= export_ptr)
|
||||
i = total_msgs-(msgbase.last_msg-export_ptr);
|
||||
|
||||
for (; i<total_msgs; i++) {
|
||||
if (js.terminated)
|
||||
break;
|
||||
|
||||
var idx = msgbase.get_msg_index(true,i);
|
||||
if (! idx) {
|
||||
log(LOG_ERROR,'! ERROR: Reading index of msg offset ['+i+'] : ['+msgbase.error+']');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip message until we get to our last one read.
|
||||
if (idx.number <= export_ptr)
|
||||
continue;
|
||||
|
||||
if (idx.number > highest)
|
||||
highest = idx.number;
|
||||
|
||||
// Only check messages from Videotex
|
||||
if (idx.from !== crc16_calc('videotex')) {
|
||||
log(LOG_DEBUG,'! IGNORING: Message offset ['+i+'] in ['+msgbase.file+']');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the message header
|
||||
var hdr = msgbase.get_msg_header(true,i);
|
||||
|
||||
// Ignore deleted messages
|
||||
if (hdr.attr&MSG_DELETE) {
|
||||
log(LOG_DEBUG,'! IGNORING: Deleted message offset ['+i+'] in ['+msgbase.file+']');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore locally posted messages
|
||||
if (! hdr.from_net_type) {
|
||||
log(LOG_DEBUG,'! IGNORING: Local message offset ['+i+'] in ['+msgbase.file+']');
|
||||
continue;
|
||||
}
|
||||
|
||||
log(LOG_DEBUG,'+ PROCESSING: Message offset ['+i+'] in ['+msgbase.file+'] from ['+hdr.from_net_addr+'] ('+hdr.subject+')');
|
||||
|
||||
var body = msgbase.get_msg_body(
|
||||
/* by_offset: */true,
|
||||
i,
|
||||
/* strip Ctrl-A */true,
|
||||
/* rfc822-encoded: */false,
|
||||
/* include tails: */false);
|
||||
|
||||
t = new File(system.temp_dir+'videotex.gpg');
|
||||
if (! t.open('w+')) {
|
||||
log(LOG_ERROR,'! ERROR: Unable to open temp file ['+system.temp_dir+'videotex.gpg'+']');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
t.write(base64_decode(body));
|
||||
t.close();
|
||||
|
||||
} catch (error) {
|
||||
log(LOG_ERROR,error);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Check if the signature is good
|
||||
result = system.exec('gpgv --keyring '+system.mods_dir+'ansitex/keys/pubring.kbx '+system.temp_dir+'videotex.gpg');
|
||||
|
||||
if (result !== 0 ) {
|
||||
log(LOG_ERROR,'! ERROR: Invalid Signature for message offset ['+i+'] in ['+msgbase.file+']');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (file_exists(system.temp_dir+'videotex.tex'))
|
||||
file_remove(system.temp_dir+'videotex.tex')
|
||||
|
||||
// Check that the signature is allowed to author the frames
|
||||
result = system.exec('gpg --homedir '+system.mods_dir+'ansitex/keys --batch --status-fd 2 -o '+system.temp_dir+'videotex.tex '+system.temp_dir+'videotex.gpg 2>'+system.temp_dir+'videotex.log');
|
||||
|
||||
if (result !== 0 ) {
|
||||
log(LOG_ERROR,'! ERROR: Failed to extract message offset ['+i+'] in ['+msgbase.file+']');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Put frame in place.
|
||||
f = new File(system.temp_dir+'videotex.tex');
|
||||
if (! f.exists || ! f.open('r')) {
|
||||
log(LOG_ERROR,'! ERROR: Failed to open frame for message offset ['+i+'] in ['+msgbase.file+']');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
frame = JSON.parse(f.read());
|
||||
x = new Frame(0);
|
||||
frame.render = x.render;
|
||||
|
||||
// @todo Figure out how to delete this duplicate code
|
||||
Object.defineProperty(frame,'page', {
|
||||
get: function() {return this.frame+this.index}
|
||||
});
|
||||
|
||||
x = null;
|
||||
|
||||
} catch (error) {
|
||||
log(LOG_ERROR,error);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the key that signed the message
|
||||
f = new File(system.temp_dir+'videotex.log');
|
||||
if (! f.exists || ! f.open('r')) {
|
||||
log(LOG_ERROR,'! ERROR: Failed to open gpg log for message offset ['+i+'] in ['+msgbase.file+']');
|
||||
continue;
|
||||
}
|
||||
|
||||
var signed = '';
|
||||
while (string = f.readln()) {
|
||||
var matches = string.match(/\s+GOODSIG\s+.*\<(.*)\>/);
|
||||
|
||||
if (matches) {
|
||||
signed = matches[1];
|
||||
}
|
||||
}
|
||||
f.close();
|
||||
|
||||
if (signed !== getKey(frame.frame,signed)) {
|
||||
log(LOG_ERROR,'! ERROR: Key ['+signed+' is not authorised for message offset ['+i+'] in ['+msgbase.file+']');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save the frame.
|
||||
log(LOG_INFO,'Updating page ['+frame.page+'] from ['+hdr.from_net_addr+'] signed with ['+signed+']');
|
||||
saveFrame(frame);
|
||||
}
|
||||
|
||||
if (ini.open(file_exists(ini.name) ? 'r+':'w+')) {
|
||||
ini.iniSetValue('videotex','export_ptr',highest);
|
||||
ini.close();
|
||||
|
||||
} else {
|
||||
log(LOG_ERROR,'! ERROR: Unable to save to INI for ['+msgbase.file+']');
|
||||
}
|
||||
}
|
||||
|
||||
var msgbase = new MsgBase(findMsgBase(null).code);
|
||||
log(LOG_DEBUG,'+ ANSITEX_EXPORT: Open message base in ['+msgbase.file+']');
|
||||
|
||||
if (! msgbase.open()) {
|
||||
log(LOG_ERROR,'! ERROR: Unable to open ['+msgbase.file+']');
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
msgBaseExport(msgbase);
|
||||
msgbase.close();
|
50
tools/frame_get.js
Normal file
50
tools/frame_get.js
Normal file
@ -0,0 +1,50 @@
|
||||
load('ansitex/load/page.js');
|
||||
load('ansitex/load/funcs.js');
|
||||
load('lz-string.js');
|
||||
|
||||
if ((argv.length > 2) || (argv.length < 1)) {
|
||||
writeln('ERROR: Need a frame ID and type');
|
||||
exit(1);
|
||||
|
||||
} else {
|
||||
writeln('Showing frame:'+argv[0]);
|
||||
}
|
||||
|
||||
var SESSION_EXT;
|
||||
|
||||
switch (argv[1]) {
|
||||
case 'TEX':
|
||||
case 'tex':
|
||||
SESSION_EXT = 'tex';
|
||||
load('ansitex/load/session/ansitex.js');
|
||||
break;
|
||||
|
||||
case 'VTX':
|
||||
case 'vtx':
|
||||
default:
|
||||
load('ansitex/load/session/viewdata.js');
|
||||
SESSION_EXT = 'vtx';
|
||||
break;
|
||||
}
|
||||
|
||||
var p = new Page();
|
||||
var po = new PageObject(argv[0]);
|
||||
|
||||
if (p.get(po)) {
|
||||
writeln('Page : '+p.name);
|
||||
writeln('- Date : '+p.date);
|
||||
writeln('- Cost : '+p.cost);
|
||||
writeln('- Owner : '+p.owner);
|
||||
writeln('- Key : '+p.key);
|
||||
writeln('- Type : '+p.type);
|
||||
writeln('- CUG : '+p.cug);
|
||||
writeln('- isPublic: '+p.isPublic);
|
||||
writeln('- isAccess: '+p.accessible);
|
||||
|
||||
} else {
|
||||
writeln('Failed to load:'+argv[0]);
|
||||
}
|
||||
|
||||
writeln('----');
|
||||
for (var x in p.raw)
|
||||
writeln(x+':'+JSON.stringify(p.raw[x])+"\r\n");
|
35
tools/frame_import.js
Normal file
35
tools/frame_import.js
Normal file
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Load a frame, and store it in the message base
|
||||
*/
|
||||
|
||||
load('ansitex/load/page.js');
|
||||
load('ansitex/load/funcs.js');
|
||||
load('lz-string.js');
|
||||
|
||||
/* parse command arguments */
|
||||
if (argv.length !== 1) {
|
||||
writeln('ERROR: Need a frame ID');
|
||||
exit(1);
|
||||
|
||||
} else {
|
||||
writeln('Importing frame:'+argv[0]);
|
||||
}
|
||||
|
||||
var po = new PageObject(argv[0]);
|
||||
|
||||
// Load ANSItex frame
|
||||
var SESSION_EXT = 'tex';
|
||||
require('ansitex/load/session/ansitex.js','SESSION_ANSITEX');
|
||||
var p = new Page();
|
||||
p.get(po);
|
||||
|
||||
// Load Viewdata frame
|
||||
var SESSION_EXT = 'vtx';
|
||||
require('ansitex/load/session/viewdata.js','SESSION_VIEWDATA');
|
||||
|
||||
var vtx = p.file_content(FRAMES_HOME+SESSION_EXT+'/'+po.toString()+'.'+CONTENT_EXT);
|
||||
|
||||
if (vtx !== undefined)
|
||||
p.raw = vtx;
|
||||
|
||||
p.save();
|
29
tools/frames_list.js
Normal file
29
tools/frames_list.js
Normal file
@ -0,0 +1,29 @@
|
||||
load('ansitex/load/funcs.js');
|
||||
load('ansitex/load/msgbases.js');
|
||||
|
||||
if (argv.length !== 1) {
|
||||
writeln('ERROR: Need a msgbase page prefix');
|
||||
exit(1);
|
||||
|
||||
} else {
|
||||
writeln('Showing frames in:'+argv[0]);
|
||||
}
|
||||
|
||||
var ma = new MsgAreas();
|
||||
var area = ma.getArea(argv[0]);
|
||||
|
||||
if (area === undefined)
|
||||
throw Error('Area:'+argv[0]+' is not defined.');
|
||||
|
||||
writeln('Opening ['+argv[0]+'] - ('+area.code+')');
|
||||
|
||||
var msgbase = area.msgbase;
|
||||
msgbase.open();
|
||||
writeln('- First:'+msgbase.first_msg);
|
||||
writeln('- Last:'+msgbase.last_msg);
|
||||
writeln('- Next:'+area.page_next);
|
||||
msgbase.close();
|
||||
|
||||
var frames = area.frames;
|
||||
for (var x in frames)
|
||||
writeln(padright(frames[x].number,4,' ')+':'+frames[x].tags);
|
26
tools/frames_tag.js
Normal file
26
tools/frames_tag.js
Normal file
@ -0,0 +1,26 @@
|
||||
load('ansitex/load/msgbases.js');
|
||||
|
||||
/**
|
||||
* Go through our messages and tag a frame id for messages without one.
|
||||
* @note: May need to run jsexec with -m 32MB to overcome memory issues
|
||||
*/
|
||||
|
||||
const ma = new MsgAreas()
|
||||
var areas = ma.managed;
|
||||
|
||||
for (var i=0;i<areas.length;i++) {
|
||||
if (argv[0] && ((argv[0] !== areas[i].code) && (argv[0] !== areas[i].page_prefix)))
|
||||
continue;
|
||||
|
||||
var total = areas[i].total_msgs;
|
||||
var frames = areas[i].frames.length;
|
||||
|
||||
writeln('Area : '+areas[i].full_name);
|
||||
writeln('- Total Messages : '+total);
|
||||
writeln('- Page Prefix : '+areas[i].page_prefix);
|
||||
writeln('- Tagged Messages : '+frames);
|
||||
writeln('- Untagged Messages: '+(total-frames));
|
||||
writeln('- Next Tag : '+areas[i].page_next);
|
||||
|
||||
areas[i].tag_msgs();
|
||||
}
|
5
tools/msgbases_list.js
Normal file
5
tools/msgbases_list.js
Normal file
@ -0,0 +1,5 @@
|
||||
load('ansitex/load/msgbases.js');
|
||||
|
||||
/* Show a list of our message bases */
|
||||
var ma = new MsgAreas();
|
||||
ma.list;
|
119
tools/save.js
Normal file
119
tools/save.js
Normal file
@ -0,0 +1,119 @@
|
||||
// ANSItex specific includes
|
||||
load('ansitex/load/defs.js');
|
||||
load('ansitex/load/funcs.js');
|
||||
|
||||
var options = loadOptions();
|
||||
|
||||
// Modes of operation:
|
||||
// -s -p x -i y = To send a stored page via the msgbase
|
||||
// {-s} -p x -i y -f file = To update (and optionally send) a page with new content
|
||||
// -all details = to create a new page
|
||||
|
||||
// Import
|
||||
send = (argv.indexOf('-s') >= 0);
|
||||
// Page
|
||||
p = getArg('-p','No page specified with -p',true);
|
||||
// Index
|
||||
i = getArg('-i','No index specified with -i',true);
|
||||
// File to convert
|
||||
file = getArg('-f','No file specified with -f',false);
|
||||
|
||||
frame = new TexFrame();
|
||||
frame.load(pageStr({frame: p,index: i}));
|
||||
|
||||
if (! send || ! frame || file) {
|
||||
if (frame.page === null) {
|
||||
frame = new TexFrame();
|
||||
frame.frame = p;
|
||||
frame.index = i;
|
||||
}
|
||||
|
||||
// Key
|
||||
key = getArg('-k','No index specified with -k',false);
|
||||
// Cost
|
||||
cost = getArg('-c','No index specified with -c',false);
|
||||
// Owner
|
||||
owner = getArg('-o','No owner specified with -o',false);
|
||||
// Owner
|
||||
type = getArg('-t','No type specified with -t',false);
|
||||
|
||||
if (file) {
|
||||
f = new File(file);
|
||||
if (! f.exists || ! f.open('r')) {
|
||||
log(LOG_ERROR,'! ERROR: Unable to open ['+file+']');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
frame.content = base64_encode(f.read());
|
||||
f.close();
|
||||
}
|
||||
|
||||
if (owner) {
|
||||
frame.owner = base64_encode(owner.replace(/\\1/g,"\1"));
|
||||
}
|
||||
|
||||
if (key) {
|
||||
frame.key = key.split(',').map(function(t){return parseInt(t)});
|
||||
|
||||
if (frame.key.length !== 10) {
|
||||
log(LOG_ERROR,'! ERROR: Must specify 10 keys with -k');
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Public
|
||||
if (argv.indexOf('-P') >= 0)
|
||||
frame.isPublic = true;
|
||||
|
||||
if (argv.indexOf('-A') >= 0)
|
||||
frame.isAccessible = true;
|
||||
|
||||
if (cost)
|
||||
frame.cost = cost;
|
||||
|
||||
if (type)
|
||||
frame.type = type; // @todo validate this is a valid type.
|
||||
|
||||
// Date
|
||||
frame.date = new Date().toISOString();
|
||||
|
||||
// Final validation
|
||||
if (! frame.owner) {
|
||||
log(LOG_ERROR,'! ERROR: No owner specified ['+file+']');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Store the frame in file
|
||||
frame.save();
|
||||
}
|
||||
|
||||
// @NOTE: We need to use a binary signature then base64 encode it, as mailers may strip 0x0a while messages are in transit.
|
||||
if (send === 1 && options.gpg_key) {
|
||||
if (! file) {
|
||||
file = system.mods_dir+'ansitex/text/'+frame.page+'.tex';
|
||||
}
|
||||
|
||||
if (! file_exists(file)) {
|
||||
log(LOG_ERROR,'! ERROR: File '+file+' doesnt exist?');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (file_exists(file+'.asc'))
|
||||
file_remove(file+'.asc')
|
||||
|
||||
result = system.exec('gpg --homedir '+system.mods_dir+'ansitex/keys --clearsign --batch --local-user '+options.gpg_key+' -s '+file);
|
||||
w = new File(file+'.asc');
|
||||
|
||||
if (w.open('r')) {
|
||||
msg = base64_encode(w.read());
|
||||
|
||||
} else {
|
||||
log(LOG_ERROR,'! ERROR: Unable to send with GPG for '+frame.page + ' Error: '+w.error);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
w.close();
|
||||
|
||||
msgBaseImport(null,frame.page,msg);
|
||||
printf('GPG Result: %s',result);
|
||||
}
|
Loading…
Reference in New Issue
Block a user