Compare commits

...

No commits in common. "master" and "ansitex" have entirely different histories.

58 changed files with 8652 additions and 144 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.idea
.editorconfig
*.debug
dev/

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View 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
View File

@ -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
View 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
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 .
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
- Optionally Edit users and change their shell.
4 - Turn off sysop password required for sysop login
- SCFG -> Toggle Options -> Require Sys Pass During Login -> No

7
keys/genkey.txt Normal file
View 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
View 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
View 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);
}

212
load/control/register.js Normal file
View File

@ -0,0 +1,212 @@
/**
* 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 + "\n\n";
msg += 'Please use the above code to validate your login to '+system.name+'.';
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
View 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;

149
load/defs.js Normal file
View File

@ -0,0 +1,149 @@
/**
* 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;
/* 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 **/
/* Information Frame, requires no response after viewed */
const FRAME_TYPE_INFO = 'i';
/* Terminate Frame, contents displayed and then carrier dropped */
const FRAME_TYPE_TERMINATE = 't';
/**
* Frame the calls an External Method
* Contents indicate the method to be called with arguments
*/
const FRAME_TYPE_EXTERNAL = 'x';
/**
* Frame calls a door
* Contents indicate the name of the door
*/
const FRAME_TYPE_DOOR = '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 = 'r';
/* Login frame, enables the user to authenticate to the system, or to a CUG */
const FRAME_TYPE_LOGIN = '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 = '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
'dynamic_fields', // array of fields
'frame', // Page ID,
'index', // Page index,
'input_fields', // array of fields
'isAccessible', // boolean
'isPublic', // boolean
'key', // array, representing our key actions
'type', // frame type
'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 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';

533
load/funcs.js Normal file
View File

@ -0,0 +1,533 @@
require('ansitex/load/defs.js','ANSITEX_HOME'); // ANSITEX definitions
// Array of page owners
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) {
'use strict';
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) {
// Get the ECHOAREA FTN AREA_TAG
case 'msg_area_areatag':
if (typeof context !== 'object') {
log(LOG_ERROR,'Unable to render ['+field+'], no context provided');
break;
}
result = context.msgbase.cfg.area_tag;
break;
// Get the ECHOAREA Description
case 'msg_area_desc':
if (typeof context !== 'object') {
log(LOG_ERROR,'Unable to render ['+field+'], no context provided');
break;
}
result = context.msgbase.cfg.description;
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 = 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 = 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 = 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 = 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 = ''+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 = ''+(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 = (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 = ''+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 = ''+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 = context.zone_name;
break;
case 'nodeid':
result = getNodeID();
break;
default:
result = (typeof bbs === 'undefined') ? '*'.repeat(Math.abs(length)) : bbs.atcode(field+(args.length ? ':'+args : ''));
}
if ((result === null) || (typeof result === 'undefined'))
result = '';
length = length ? length : result.length;
//log(LOG_DEBUG,' - result length ['+result.length+'] desired ('+length+')');
if (result.length < Math.abs(length))
result = (length < 0) ? padright(result,Math.abs(length),pad) : padleft(result,length,pad);
else if (result.length > Math.abs(length))
result = result.substr(0,Math.abs(length));
log(LOG_DEBUG,'* ATCODE ['+field+'] ('+length+'|"'+pad+'") returns ['+result+']');
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;

428
load/msgbases.js Normal file
View File

@ -0,0 +1,428 @@
const PAGE_LENGTH = 4; // The size of our page tag as stored in the msgbase for echomail/netmail
const PAGE_LAST_KEY = 'last_page'; // Last page which has the latest message
const MAX_PAGE_NUM = 9999; // Maximum page number. @todo Can this be changed to '9'.repeat(PAGE_LENGTH)?
// Our message bases
function MsgAreas() {
'use strict';
this.areas = [];
this.areas_excluded = [];
var zone_id;
var zone_name;
var ma;
for(var g in msg_area.grp_list) {
if (msg_area.grp_list[g].name.indexOf(':') !== -1) {
zone_id = msg_area.grp_list[g].name.split(':')[0];
zone_name = msg_area.grp_list[g].name.split(':')[1];
for (var a in msg_area.grp_list[g].sub_list) {
if (msg_area.grp_list[g].sub_list[a].name.indexOf(':') !== -1) {
ma = new MsgArea();
ma.zone_id = zone_id;
ma.zone_name = zone_name;
ma.area_id = msg_area.grp_list[g].sub_list[a].name.split(':')[0];
ma.area_name = msg_area.grp_list[g].sub_list[a].name.split(':')[1];
ma.code = msg_area.grp_list[g].sub_list[a].code;
this.areas.push(ma);
} else {
this.areas_excluded.push(zone_name+':'+msg_area.grp_list[g].sub_list[a].name);
}
}
} else {
zone_name = msg_area.grp_list[g].name;
for (var a in msg_area.grp_list[g].sub_list) {
this.areas_excluded.push(zone_name+':'+msg_area.grp_list[g].sub_list[a].name);
}
}
}
Object.defineProperty(this,'list',{
get: function() {
writeln('Areas that we are NOT managing mail:'+this.areas_excluded.length);
writeln('Areas that we ARE managing mail:'+this.areas.length);
for(var x in this.areas_excluded) {
writeln(x+':'+((this.areas_excluded[x].area_name === undefined)
? this.areas_excluded[x]
: JSON.stringify(this.areas_excluded[x])));
}
}
});
}
function MsgArea() {
this.zone_id = undefined;
this.zone_name = undefined;
this.area_id = undefined;
this.area_name = undefined;
this.msgbase = undefined;
this.headers = undefined;
this.tagged_list = undefined;
this.untagged_list = undefined;
this.grp_number = undefined;
this.subnum = undefined;
/**
* Build a MsgArea once we are given the code
*/
Object.defineProperty(this,'code',{
set: function(code) {
this.msgbase = new MsgBase(code);
try {
if (this.msgbase.open()) {
headers = this.msgbase.get_all_msg_headers(false,false) || [];
// Just take the last MAX_MESSAGES
this.headers = Object.keys(headers).slice(-(MAX_PAGE_NUM+1)).map(function(key) { return headers[key]; });
headers = undefined;
this.msgbase.close();
} else {
log(LOG_ERROR,code+' cannot be opened:'+this.msgbase.error);
this.headers = [];
}
} catch (e) {
log(LOG_ERROR,code+' cannot be opened:'+e.message);
this.headers = [];
}
}
});
// Get Area's full name
Object.defineProperty(this,'full_name',{
get: function() {
return this.zone_name+':'+this.area_name;
}
});
// 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;
}
});
// List untagged messages
Object.defineProperty(this,'list_untagged',{
get: function() {
if (this.untagged_list === undefined) {
this.untagged_list = [];
if (! this.headers)
return this.untagged_list;
for(var x in this.headers) {
if ((! this.headers[x].tags) || (this.headers[x].tags.length !== PAGE_LENGTH)) {
this.untagged_list.push(this.headers[x]);
write(); // @todo This is needed for this to work?
}
}
}
return this.untagged_list;
}
});
// Get Next page number
Object.defineProperty(this,'page_next',{
get: function() {
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);
}
var page = f.iniGetValue('prefix:'+this.page_prefix,PAGE_LAST_KEY)
f.close();
return page ? page : '0'.repeat(PAGE_LENGTH);
},
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;
},
});
}
/**
* 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;
}
/**
* 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;
}
/**
* 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:'+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;
}
/**
* 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;
}
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] : [];
}
MsgArea.prototype.MessageNext = function(page) {
var x = null;
if (! page)
return undefined;
var msgid = page.substr(7,4);
for(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)];
}
MsgArea.prototype.MessagePrev = function(page) {
var prev = null;
var x = null;
if (! page)
return undefined;
var msgid = page.substr(7,4);
for(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.list_untagged;
writeln("We have "+msgs.length+" messages to tag.");
// See if we need to tag something
if (! msgs.length)
return;
if (! this.msgbase.open()) {
writeln(code+' cannot be opened:'+this.msgbase.error);
return false;
}
var page_next = this.page_next;
for(var x in msgs) {
msgs[x].tags = (''+(page_next)).padStart(4,'0');
if(! this.msgbase.put_msg_header(msgs[x].number,msgs[x])) {
writeln('ERROR:'+this.msgbase.error);
} else {
page_next++;
if (page_next > MAX_PAGE_NUM)
page_next = 0;
}
}
this.msgbase.close();
this.page_next = page_next;
return true;
}
MsgArea.prototype.page = function(msgid) {
return '1'+this.page_prefix+msgid;
}
MsgAreas.prototype.getArea = function(area) {
log(LOG_DEBUG,'- AREA:'+JSON.stringify(area));
if (area === undefined)
return undefined;
var zone = (''+area).substr(1,4);
var echo = (''+area).substr(5,2);
log(LOG_DEBUG,' - zone:'+zone);
log(LOG_DEBUG,' - echo:'+echo);
return this.areas.filter(function(x) {
return x.zone_id === zone && x.area_id === echo;
})[0]
}
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;
}
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;
}

1386
load/page.js Normal file

File diff suppressed because it is too large Load Diff

995
load/qrcode-make.js Normal file
View 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
View 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
View File

@ -0,0 +1,596 @@
const SESSION_ANSITEX = (1<<1);
var SESSION_EXT = 'tex';
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,'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) {
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
View File

@ -0,0 +1,650 @@
const SESSION_VIEWDATA = (1<<2);
var SESSION_EXT = 'vtx';
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(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));
if (y > 0)
write_raw(VIEWDATA_DOWN.repeat(y));
}
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;

1066
load/windows.js Normal file

File diff suppressed because it is too large Load Diff

20
logon.js Normal file
View 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);

1288
main.js Normal file

File diff suppressed because it is too large Load Diff

8
text/frames_check.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
find tex vtx -maxdepth 1 -type f | awk -F. '{print $1}'|awk -F/ '{print $2}'|sort|uniq| while read i; do
[ "$i" == "tex/:" -o "$i" == "vtx/:" ] && continue
echo "======= ${i} ======="
jsexec -n ansitex/tools/frames_check $i
done

1
text/tex/198a Normal file
View 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
View 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"}

1
text/tex/1a Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":1,"index":"a","owner":0,"cost":0,"content":"G1swbSAgICAgIBtbMW0uG1swbSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1szMW3axBtbMW3cG1swOzMybdrEG1sxbdwbWzA7MzRt2sQbWzFt3BtbMDszNm0gG1sxOzMzbd8bWzBtIBtbMzA7NDdt2sLc2sTc2iDcIBtbMzc7NDBtDQogG1sxbdrCv8K/v9q/INrCv8K/2r+/2htbMG0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMW2zG1swOzMxbd/bG1sxOzMybbMbWzA7MzZtIBtbMzJt2xtbMTszNG0uXBtbMDszNG3cG1szM23eG1sxbdsbWzBtIBtbMzA7NDdtILMgw1/c2t/cIBtbMzc7NDBtDQogsyCzwrSzs7MgsyCzw9mzs7OzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzBtxMTExMTExMTExMTEG1swOzMwOzQ3bdzc3Nzc3Nzc3NwbWzM3OzQwbQ0KIBtbMTszMG3AINnBwdnZ2SDAINnB2dnZwdkbWzBtICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMG1ub2RlOhtbMG0gG1sxOzMybRtYbm9kZWlkOy0xMRtcG1swbQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzBtZGF0ZTogG1szMm0bWERBVEU6JVktJWItJWQ7LTExG1wbWzBtDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMG10aW1lOiAbWzMybRtYVElNRTstMTEbXBtbMG0NChtbMTszMG3ExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMSzxMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExBtbMG0gG1sxOzMybTEbWzM3bSBGaWRvIE1lc3NhZ2UgTmV0d29ya3MbWzBtICAgICAgICAgICAgICAgG1sxOzMwbbMbWzBtICAgIBtbMTszMG1Vc2VmdWwgUGFnZXMbWzBtDQogG1szMm0yG1sxOzM3bSAbWzBtQkJTIERvb3IgR2FtZXMbWzFtIBtbMDszMW1bG1sxbUNvbWluZyBTb29uG1swOzMxbV0bWzM3bSAgICAgICAgG1sxOzMwbbMbWzBtDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzBtsxtbMG0gG1sxOzMybSoxMDAxMiMbWzBtIBtbMW1Eb3ZlbmV0IE1lc3NhZ2UgTmV0d29yaxtbMG0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMG2zG1swbSAbWzE7MzJtKjEwMDIxIxtbMG0gG1sxbUZTWG5ldBtbMG0gG1sxbU1lc3NhZ2UgTmV0d29yaxtbMG0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMG2zG1swbSAbWzE7MzJtKjExMzM3IxtbMG0gG1sxbVRRV25ldBtbMG0gG1sxbU1lc3NhZ2UgTmV0d29yaxtbMG0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMG2zG1swbQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzMwbbMbWzBtIBtbMTszMm0qNTE2IxtbMG0gICAbWzE7MzFtQRtbMzJtThtbMzRtUxtbMzNtSRtbMDszMDs0N210ZXgbWzM3OzQwbQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzMwbbMbWzBtIBtbMTszMm0qNTE2MiMgIBtbMzdtTmF2aWdhdGluZxtbMzNtIBtbMzFtQRtbMzJtThtbMzRtUxtbMzNtSRtbMDszMDs0N210ZXgbWzM3OzQwbQ0KIBtbMTszMm05G1szN20gQWJvdXQgG1szMW1BG1szMm1OG1szNG1TG1szM21JG1swOzMwOzQ3bXRleBtbMzc7NDBtICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzBtsxtbMG0gG1sxOzMybSo5NSMgICAgG1szN21IZWxwG1swbQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzMwbbMbWzBtIBtbMTszMm0qOTkjG1szN20gICAgTG9nIE9mZhtbMG0NChtbMW0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzMwbbMbWzBtDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzBtsxtbMG0NCg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzU7MzJtKjAjG1swOzFtIHRvIGdldCBiYWNrIGhlcmUgYW55dGltZRtbMG0NCg==","isPublic":1,"isAccessible":1,"type":"i","key":[null,11,2,null,null,null,null,null,null,516],"date":"2022-05-13T00:00:00.000Z"}

1
text/tex/980a Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":"980","index":"a","owner":9,"cost":0,"content":"G1swbSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMzFt2sQbWzFt3BtbMDszMm3axBtbMW3cG1swOzM0bdrEG1sxbdwbWzA7MzZtIBtbMTszM23fG1swbSAbWzMwOzQ3bdrC3NrE3Nog3CAbWzM3OzQwbQ0KIBtbMW3aIL/Cv7Pav9q/2sK/wr8bWzBtICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzFtsxtbMDszMW3f2xtbMTszMm2zG1swOzM2bSAbWzMybdsbWzE7MzRtLlwbWzA7MzRt3BtbMzNt3htbMW3bG1swbSAbWzMwOzQ3bSCzIMNf3Nrf3CAbWzM3OzQwbQ0KILOzs8PZs7Mgs7OzILPD2SAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzMwbcTExMTExMTExMTExBtbMDszMDs0N23c3Nzc3Nzc3NzcG1szNzs0MG0NCiAbWzE7MzBtwMHZwdnAwNnA2cAg2cHZG1swbSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzBtbm9kZTobWzBtIBtbMTszMm0bWG5vZGVpZDstMTEbXBtbMzRtDQobWzBtICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzBtZGF0ZTobWzA7MzJtIBtbMW0bWERBVEU6JVktJWItJWQ7LTExG1wbWzBtDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMG10aW1lOiAbWzMybRtYVElNRTstMTEbXBtbMG0NCg0KIBtbMW1XZWxjb21lLCB5b3UgaGF2ZSBjb25uZWN0ZWQgdG8gG1szMW1BG1szMm1OG1szNG1TG1szM21JG1swOzMwOzQ3bXRleBtbMTszNzs0MG0gYSBCQlMgdGhhdCBpcyBiYXNlZCBvbiB0aGUbWzBtDQogG1sxbTE5ODAncyBWaWRlb3RleCBzZXJ2aWNlLCBidXQgdXNpbmcgQU5TSS4bWzBtDQoNCiAbWzFtRGVwZW5kaW5nIG9uIHdoaWNoIGNvdW50cnkgeW91IGxpdmUgaW4gdGhlIFZpZGVvdGV4IHNlcnZpY2Ugd2FzIGNhbGxlZBtbMG0NCiAbWzFtVmlhdGVsIChBVSksIFByZXN0ZWwgKFVLKSwgTWluaXRleCAoRlIpLCBUZWxpZG9uIChDQSksIEliZXJ0ZXggKFNQKSwgZXRjG1swbQ0KG1sxbQ0KG1swbSAbWzFtSWYgeW91IGdvdCBoZXJlIGJ5IG1pc3Rha2UsIHlvdSBzaG91bGQgZGlzY29ubmVjdCBub3csIG90aGVyd2lzZSwgeW91IGNhbhtbMG0NChtbMW0gcHJlc3MbWzBtIBtbMTs1OzMybTAbWzA7MW0gdG8gZ2V0IHRvIHRoZSBsb2dpbiBzY3JlZW4uG1swbQ0KG1sxbQ0KG1swbQ0K","isPublic":1,"isAccessible":1,"type":"i","key":[0,982,null,null,null,null,null,null,null,null],"date":"2020-07-05T12:57:03.790Z"}

1
text/tex/981a Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":981,"index":"a","owner":9,"cost":0,"content":"G1swbSAgICAgICAbWzFt+htbMG0gICAgICAgICAgG1sxbfobWzBtICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzMxbdrEG1sxbdwbWzA7MzJt2sQbWzFt3BtbMDszNG3axBtbMW3cG1swOzM2bSAbWzE7MzNt3xtbMG0gG1szMDs0N23awtzaxNzaINwgG1szNzs0MG0NCiAbWzFtwr/Cv8K/v9q/wy3Cv8K/wy2/2r/avxtbMG0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzMxbbMbWzA7MzFt39sbWzE7MzJtsxtbMDszNm0gG1szMm3bG1sxOzM0bS5cG1swOzM0bdwbWzMzbd4bWzFt2xtbMG0gG1szMDs0N20gsyDDX9za39wgG1szNzs0MG0NCiCzIMPZs7OzwL+zILMgwrSzILOzs7OzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMG3ExMTExMTExMTExMQbWzA7MzA7NDdt3Nzc3Nzc3Nzc3BtbMzc7NDBtDQogG1sxOzMwbdkbWzM2bSAbWzMwbcHZwbTZwNnA2dkbWzM2bSAbWzMwbcHBwNnZwNnZ2RtbMG0NCiAgICAgG1sxOzMwbcTZG1swbQ0KG1sxbSBQbGVhc2UgY29tcGxldGUgdGhlIHVzZXIgcmVnaXN0cmF0aW9uOhtbMG0NCg0KIBtbMTszMG2zG1swbSAgICAgICAbWzE7MzFtRW1haWw6G1swbSAbWzFtG19FTUFJTDs2MHQ7+RtcG1swbSAgG1sxOzMwbbMbWzBtDQogG1sxOzMwbbMbWzBtICAgICAbWzE7MzFtVXNlciBJRDobWzBtIBtbMW0bX1VTRVI7MjV0O/kbXBtbMG0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzMwbbMbWzBtDQogG1sxOzMwbbMbWzBtICAgIBtbMTszMW1QYXNzd29yZDobWzBtIBtbMW0bX1BBU1M7NDBwO/kbXBtbMG0gICAgICAgICAgICAgICAgICAgICAgG1sxOzMwbbMbWzBtDQogG1sxOzMwbbMbWzBtICAgG1sxOzMxbUZ1bGwgTmFtZTobWzBtIBtbMW0bX0ZVTExOQU1FOzI1dDv5G1wbWzBtICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMG2zG1swbQ0KIBtbMTszMG2zG1swbSAgICAgICAbWzE7MzFtVG9rZW46G1swbSAbWzFtG19UT0tFTjs2dDv5G1wbWzBtICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzBtsxtbMG0NCg0KIBtbMW1UbyBjbGVhciB0aGUgY3VycmVudCBmaWVsZCwgdXNlIBtbMzJtKiobWzM3bS4bWzBtDQoNCiAbWzFtUmVnaXN0ZXJpbmcgYW5kIHVzaW5nIHRoaXMgc3lzdGVtLCB5b3UgYWdyZWUgdG8gYWJpZGUgYnkgdGhlIHN5c3RlbSBydWxlcy4bWzBtDQogG1sxbVlvdSBjYW4gdmlldyB0aG9zZSBydWxlcyBvbiBwYWdlIBtbMzJtKjk4OCMbWzM3bS4bWzBtDQo=","isPublic":1,"isAccessible":1,"type":"r","key":[980,"register",null,null,null,null,null,null,null,null],"date":"2020-07-09T11:42:40.643Z"}

1
text/tex/981b Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":"981","index":"b","owner":9,"cost":0,"content":"G1swbSAbWzFt2iC/wr+z2r/av9rCv8K/G1swbSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1szMW3axBtbMW3cG1swOzMybdrEG1sxbdwbWzA7MzRt2sQbWzFt3BtbMDszNm0gG1sxOzMzbd8bWzBtIBtbMzA7NDdt2sLc2sTc2iDcIBtbMzc7NDBtDQogs7Ozw9mzsyCzs7Mgs8PZICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzFtsxtbMDszMW3f2xtbMTszMm2zG1swOzM2bSAbWzMybdsbWzE7MzRtLlwbWzA7MzRt3BtbMzNt3htbMW3bG1swbSAbWzMwOzQ3bSCzIMNf3Nrf3CAbWzM3OzQwbQ0KIBtbMTszMG3AwdnB2cDA2cDZwCDZwdkbWzBtICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzBtxMTExMTExMTExMTEG1swOzMwOzQ3bdzc3Nzc3Nzc3NwbWzM3OzQwbQ0KDQogG1sxbVdlbGNvbWUgdG8gG1szMW1BG1szMm1OG1szNG1TG1szM21JG1swOzMwOzQ3bXRleBtbMTszNzs0MG0gYSBidWxsZXRpbiBib2FyZCBzeXN0ZW0gYmFzZWQgb24gYSBWaWRlb3RleCBidXQgd2l0aCBBTlNJIRtbMG0NCg0KG1sxbSBZb3VyIHVzYWdlIGhlcmUgaXMgZ292ZXJuZWQgYnkgc29tZSBydWxlcywgd2hpY2ggeW91IGNhbiBzZWUgb24gcGFnZSAbWzMybSo5ODgjG1szN20uG1swbQ0KDQobWzFtIFRvIGdldCB0byB0aGUgaG9tZSBwYWdlIGF0IGFueXRpbWUsIHVzZSAbWzMybSowIxtbMzdtLhtbMG0NCg0KG1sxbSBJZiB5b3UgYXJlIG5vdCBmYW1pbGlhciB3aXRoIFZpZGVvdGV4LCB0aGVuIHlvdSBtaWdodCBsaWtlIHRvIHZpc2l0IHRoZSBoZWxwG1swbQ0KG1sxbSBwYWdlcyBhdCAbWzMybSo1MTYjLhtbMG0NCg0KG1sxbSBOYXZpZ2F0aW5nIGFyb3VuZCB0aGlzIEJCUyBpcyBkb25lIGJ5IHVzaW5nIHlvdXIga2V5Ym9hcmQgbnVtYmVyIGtleXMuIFRvIGdvG1swbQ0KG1sxbSB0byBhIHBhZ2UgZGlyZWN0bHksIHByZXNzIBtbMzJtKhtbMzdtIGZvbGxvd2VkIGJ5IHRoZSBwYWdlIG51bWJlciwgYW5kIHRoZW4gG1szMm0jG1szN20gdG8bWzBtDQobWzFtIHRlbGVwb3J0IHRoZXJlLhtbMG0NCg0KG1sxbSBTb21lIHBhZ2VzIHByb3ZpZGUgbmF2aWdhdGlvbiBvbiB0aGUgcGFnZSwgdXNpbmcgdGhlIGtleXMgG1szMm0wG1szN20tG1szMm05G1szN20uIE9uIHBhZ2UbWzBtDQobWzFtIG5hdmlnYXRpb24gaXMgbm9ybWFsbHkgb2J2aW91cywgYnV0IGlmIHlvdSB0cnkgaXQgYW5kIHRoZXJlIGlzbnQgYW55dGhpbmcgeW91G1swbQ0KG1sxbSB3aWxsIGdldCBhIG5vdGlmaWN0aW9uIG9uIHRoZSBzdGF0dXMgbGluZSBiZWxvdy4gTW9yZSBpbmZvcm1hdGlvbiBvbiAbWzMybSo1MTYjLhtbMG0NCg0KG1sxbSBQcmVzcyAbWzMybTAbWzM3bSB0byBnZXQgdGhlIGhvbWUgcGFnZS4bWzBtDQo=","isPublic":1,"isAccessible":1,"type":"i","key":[0,null,null,null,null,null,null,null,null,null],"date":"2023-12-30T12:57:03.790Z"}

1
text/tex/983a Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":983,"index":"a","owner":9,"cost":0,"content":"G1swbRtbMzFt2sQbWzFt3BtbMDszMm3axBtbMW3cG1swOzM0bdrEG1sxbdwbWzA7MzZtIBtbMTszM23fG1swbSAbWzMwOzQ3bdrC3NrE3Nog3CAbWzM3OzQwbQ0KG1sxOzMxbbMbWzA7MzFt39sbWzE7MzJtsxtbMDszNm0gG1szMm3bG1sxOzM0bS5cG1swOzM0bdwbWzMzbd4bWzFt2xtbMG0gG1szMDs0N20gsyDDX9za39wgG1szNzs0MG0NChtbMTszMG3ExMTExMTExMTExMQbWzA7MzA7NDdt3Nzc3Nzc3Nzc3BtbMzc7NDBtDQoNChtbMW1Mb2dpbiAbWzMxbUZBSUxFRBtbMzdtLhtbMG0NCg0KG1sxbVRvIHRyeSBhZ2FpbiwgcGxlYXNlIHByZXNzIBtbMzJtMBtbMG0NCg==","isPublic":1,"isAccessible":1,"type":"r","key":[0,null,null,null,null,null,null,null,null,null],"frame_fields": [],"date":"2020-08-09T11:42:40.643Z"}

1
text/tex/98a Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":98,"index":"a","owner":9,"cost":0,"content":"G1swbSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMzFt2sQbWzFt3BtbMDszMm3axBtbMW3cG1swOzM0bdrEG1sxbdwbWzA7MzZtIBtbMTszM23fG1swbSAbWzMwOzQ3bdrC3NrE3Nog3CAbWzM3OzQwbQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzMxbbMbWzA7MzFt39sbWzE7MzJtsxtbMDszNm0gG1szMm3bG1sxOzM0bS5cG1swOzM0bdwbWzMzbd4bWzFt2xtbMG0gG1szMDs0N20gsyDDX9za39wgG1szNzs0MG0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMG3ExMTExMTExMTExMQbWzA7MzA7NDdt3Nzc3Nzc3Nzc3BtbMzc7NDBtDQoNCg0KDQoNCg0KDQoNCg0KDQogICAgICAgICAbWzFtLiAgICAgLiAgIBtbMzBtsxtbMG0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzBtsxtbMG0NCiAgICAgICAbWzFt2r+/wr/avyC/2r8bWzBtIBtbMTszMG2zG1swbSAbWzE7MzFtVVNFUjobWzBtIBtbMW0bX1VTRVI7MjV0O/kbXBtbMG0gICAgICAgICAgICAgICAgG1sxOzMwbbMbWzBtDQogICAgICAgwL+zs7OzsyCzs7MgG1sxOzMwbbMbWzBtICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzMwbbMbWzBtDQogICAgICAgG1sxOzMwbcDZ2cG02dkbWzM3bSAbWzMwbdnZ2RtbMzdtIBtbMzBtsxtbMG0gG1sxOzMxbVBBU1M6G1swbSAbWzFtG19QQVNTOzQwcDv5G1wbWzBtIBtbMTszMG2zG1swbQ0KICAgICAgICAgIBtbMTszMG3E2RtbMG0gICAgICAgG1sxOzMwbbMbWzBtICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzMwbbMbWzBtDQoNCg0KICAgICAgICAgICAgIBtbMTszMG1UbyByZWdpc3RlciBhbiBhY2NvdW50IGVudGVyG1swbSAbWzFtTkVXG1swbSAbWzE7MzBtZm9yIHRoZSB1c2VyIG5hbWUbWzBtDQogICAgICAgICAgICAgICAgG1sxOzMwbVVzZRtbMG0gG1sxOzMybSoqG1swbSAbWzE7MzBtdG8gY2xlYXIgeW91ciBpbnB1dBtbMG0gG1sxOzMybSowMBtbMG0gG1sxOzMwbXRvIHN0YXJ0IGFnYWluG1swbQ0K","isPublic":1,"isAccessible":1,"type":"l","key":[null,"login",null,null,null,null,null,null,null,null],"date":"2020-07-08T05:17:35.174Z"}

1
text/tex/98b Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":98,"index":"b","owner":9,"cost":0,"content":"G1swbSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMzFt2sQbWzFt3BtbMDszMm3axBtbMW3cG1swOzM0bdrEG1sxbdwbWzA7MzZtIBtbMTszM23fG1swbSAbWzMwOzQ3bdrC3NrE3Nog3CAbWzM3OzQwbQ0KIBtbMW3aIL/Cv7Pav9q/2sK/wr8bWzBtICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzFtsxtbMDszMW3f2xtbMTszMm2zG1swOzM2bSAbWzMybdsbWzE7MzRtLlwbWzA7MzRt3BtbMzNt3htbMW3bG1swbSAbWzMwOzQ3bSCzIMNf3Nrf3CAbWzM3OzQwbQ0KILOzs8PZs7Mgs7OzILPD2SAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzMwbcTExMTExMTExMTExBtbMDszMDs0N23c3Nzc3Nzc3NzcG1szNzs0MG0NCiAbWzE7MzBtwMHZwdnAwNnA2cAg2cHZG1swbSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzBtbm9kZTobWzBtIBtbMTszMm0bWG5vZGVpZDstMTEbXBtbMG0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzMwbWRhdGU6IBtbMTszMm0bWERBVEU6JVktJWItJWQ7LTExG1wbWzBtDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMG10aW1lOiAbWzE7MzJtG1hUSU1FOy0xMRtcG1swbQ0KDQogSGkbWzFtIBtYUkVBTE5BTUU7MjAbXA0KG1swbQ0KIFdlbGNvbWUgdG8bWzE7MzBtIBtbMzFtQRtbMzJtThtbMzRtUxtbMzNtSRtbMDszMDs0N210ZXgbWzM3OzQwbSwgYSBCQlMgaW5zcGlyZWQgYnkgVmlkZW90ZXgvVmlld2RhdGEgb2YgdGhlIDE5ODAncyBhbmQNCiAxOTkwJ3MuDQoNCiBUaGlzIEJCUyBpbnRlcmZhY2UgaXMgYnVpbGQgb24gdG9wIG9mIFN5bmNocm9uZXQgQkJTLCBhbmQgbWlnaHQgYmUgc29tZXRoaW5nDQogZGlmZmVyZW50IHRvIHdoYXQgeW91IGFyZSB1c2VkIHRvIHdpdGggb3RoZXIgU3luY2hyb25ldCBCQlMnLg0KG1sxOzMxbQ0KG1swbSBOYXZpZ2F0aW9uIGlzIHZpYSBwYWdlcywgd2hpY2ggY2FuIGJlIGFjY2Vzc2VkIHZpYSAbWzE7MzJtKhtbMDszMm1wYWdlbnVtG1sxbSMbWzBtLiBJbmZvcm1hdGlvbiBpcw0KIGF2YWlsYWJsZSB2aWEgcGFnZSAbWzE7MzJtKjUxNiMbWzBtLCB3aGljaCB3aWxsIGhlbHAgeW91IGdldCBzdGFydGVkIGlmIHlvdSBuZWVkIGl0Lg0KIFRvIGdldCB0byB0aGUgaG9tZSBwYWdlIGF0IGFueSB0aW1lLCB1c2UgG1sxOzMybSowIxtbMG0uDQoNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1sxbVByZXNzG1swbSAbWzE7NTszMm0wG1swOzE7MzNtIBtbMzdtTWFpbiBNZW51G1swbQ0K","isPublic":0,"isAccessible":1,"type":"i","key":[1,null,null,null,null,null,null,null,null,null],"date":"2020-07-08T01:30:48.608Z"}

1
text/tex/998a Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":998,"index":"a","owner":9,"cost":0,"content":"G1swbRtbMzFt2sQbWzFt3BtbMDszMm3axBtbMW3cG1swOzM0bdrEG1sxbdwbWzA7MzZtIBtbMTszM23fG1swbSAbWzMwOzQ3bdrC3NrE3Nog3CAbWzM3OzQwbQ0KG1sxOzMxbbMbWzA7MzFt39sbWzE7MzJtsxtbMDszNm0gG1szMm3bG1sxOzM0bS5cG1swOzM0bdwbWzMzbd4bWzFt2xtbMG0gG1szMDs0N20gsyDDX9za39wgG1szNzs0MG0NChtbMTszMG3ExMTExMTExMTExMQbWzA7MzA7NDdt3Nzc3Nzc3Nzc3BtbMzc7NDBtDQoNChtbMW1IbW0sIGEgc3lzdGVtIBtbMzFtRVJST1IbWzM3bSBvY2N1cnJlZC4bWzBtDQobWzFtTm9kZSAgOiAbWzMxbRtYbm9kZWlkOzgbXBtbMG0NChtbMW1TeXN0ZW06IBtbMzFtG1hCQlM7MTAbXBtbMG0NCg0KG1sxbUlmIHRoaXMga2VlcHMgaGFwcGVuaW5nLCB5b3UgbWF5IG5lZWQgdG8gdGVsbCB0aGUgc3lzdGVtIGFkbWluaXN0cmF0b3IbWzBtDQobWzFtdmlhIHBhZ2UgG1szMm0qMDgbWzBtDQoNChtbMW1UbyB0cnkgYWdhaW4sIHBsZWFzZSBwcmVzcyAbWzMybTAbWzBtDQo=","isPublic":1,"isAccessible":1,"type":"i","key":[0,null,null,null,null,null,null,null,null,null],"date":"2021-02-18T00:52:54.117Z"}

1
text/tex/999a Normal file

File diff suppressed because one or more lines are too long

1
text/tex/999b Normal file

File diff suppressed because one or more lines are too long

1
text/tex/99a Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":99,"index":"a","owner":9,"cost":0,"content":"G1swbSAgICAgICAgG1sxbdq/G1swbSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1szMW3axBtbMW3cG1swOzMybdrEG1sxbdwbWzA7MzRt2sQbWzFt3BtbMDszNm0gG1sxOzMzbd8bWzBtIBtbMzA7NDdt2sLc2sTc2iDcIBtbMzc7NDBtDQogG1sxbcK/2r/av9q0w7+/2sK/G1swbSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMW2zG1swOzMxbd/bG1sxOzMybbMbWzA7MzZtIBtbMzJt2xtbMTszNG0uXBtbMDszNG3cG1szM23eG1sxbdsbWzBtIBtbMzA7NDdtILMgw1/c2t/cIBtbMzc7NDBtDQogs7Ozs7Ozs7Ozs7Ozw9kgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzBtxMTExMTExMTExMTEG1swOzMwOzQ3bdzc3Nzc3Nzc3NwbWzM3OzQwbQ0KIBtbMTszMG3BtMDZwNnA2cDZwLTB2RtbMG0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMG1ub2RlOhtbMG0gG1sxOzMybRtYbm9kZWlkOy0xMRtcG1swbQ0KIBtbMTszMG3E2RtbMzFtICAgICAgICAbWzMwbcTZG1swbSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzBtZGF0ZTobWzBtIBtbMTszMm0bWERBVEU6JVktJWItJWQ7LTExG1wbWzBtDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMG10aW1lOhtbMG0gG1sxOzMybRtYVElNRTstMTEbXBtbMG0NCg0KIFlvdSBhcmUgYWJvdXQgdG8gZGlzY29ubmVjdCBmcm9tIBtbMTszMW1BG1szMm1OG1szNG1TG1szM21JG1swOzMwOzQ3bXRleBtbMzc7NDBtIBtbMW0bWFJFQUxOQU1FOzIwG1wbWzBtDQoNCiBJZiB5b3Ugd2FudGVkIHRvIHN0YXkgb25saW5lLCB5b3UgY2FuIHByZXNzIBtbMTszMm0qMCMbWzBtIHRvIGdldCBiYWNrIHRvIHRoZSBtYWluIG1lbnUNCg0KIE90aGVyd2lzZSBwcmVzcyAbWzE7NTszMm0jG1swOzE7MzNtIBtbMG10byBkaXNjb25uZWN0IG5vdy4NCg0KDQoNCg0KDQoNCg0KDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTs1OzMybSowIxtbMDsxbSB0byBnZXQgYmFjayB0byB0aGUgTWFpbiBNZW51G1swbQ0K","isPublic":1,"isAccessible":1,"type":"i","key":[0,null,null,null,null,null,null,null,null,null],"date":"2020-07-15T12:15:47.742Z"}

1
text/tex/99b Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":99,"index":"b","owner":9,"cost":0,"content":"G1swbSAgICAgICAgG1sxbdq/G1swbSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1szMW3axBtbMW3cG1swOzMybdrEG1sxbdwbWzA7MzRt2sQbWzFt3BtbMDszNm0gG1sxOzMzbd8bWzBtIBtbMzA7NDdt2sLc2sTc2iDcIBtbMzc7NDBtDQogG1sxbcK/2r/av9q0w7+/2sK/G1swbSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMW2zG1swOzMxbd/bG1sxOzMybbMbWzA7MzZtIBtbMzJt2xtbMTszNG0uXBtbMDszNG3cG1szM23eG1sxbdsbWzBtIBtbMzA7NDdtILMgw1/c2t/cIBtbMzc7NDBtDQogs7Ozs7Ozs7Ozs7Ozw9kgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzBtxMTExMTExMTExMTEG1swOzMwOzQ3bdzc3Nzc3Nzc3NwbWzM3OzQwbQ0KIBtbMTszMG3BtMDZwNnA2cDZwLTB2RtbMG0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMG1ub2RlOhtbMG0gG1sxOzMybRtYbm9kZWlkOy0xMRtcG1swbSAgG1sxOzMwbcTZG1szMW0gICAgICAgIBtbMzBtxNkbWzBtICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMG1kYXRlOhtbMG0gG1sxOzMybRtYREFURTolWS0lYi0lZDstMTEbXBtbMG0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzBtdGltZTobWzBtIBtbMTszMm0bWFRJTUU7LTExG1wbWzBtDQoNCiBUaGFua3MgZm9yIHZpc2l0aW5nIBtbMTszMW1BG1szMm1OG1szNG1TG1szM21JG1swOzMwOzQ3bXRleBtbMzc7NDBtIBtbMW0bWFJFQUxOQU1FOzIwG1wbWzBtDQoNCiBIb3BlIHRvIHNlZSB5b3UgYWdhaW4uLi4NCg==","isPublic":1,"isAccessible":1,"type":"t","key":[1,null,null,null,null,null,null,null,null,null],"date":"2020-07-08T01:48:01.797Z"}

1
text/vtx/1a Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":1,"index":"a","owner":0,"cost":0,"content":"FzdjY2tzM3d7IzM1ICAgICAgIBEsbBJ8bBR8LBMsFzdrfyMzN2sjNRc1ampqIDQ1aiA1NSAgICAgICARf2sSf2oUL3wTfxc1aG8gPD0kLDUXdXp6enBxdXpwdTUgICAgICAgES8uEi8qFCwvEy8XdXB6cHF1enA1ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFZpZGVvdGV4ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAU5PVEU6IEN1cnJlbnQgdW5kZXIgY29uc3RydWN0aW9uICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICACKjUxNl8HQWJvdXQgQW5zaVRFWCAgICAgICAgICAgICAgICAgICAgAio5ODhfB1J1bGVzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICACKjk5XwdEaXNjb25uZWN0ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA==","isPublic":1,"isAccessible":1,"type":"i","key":[null,91,null,11,null,97,null,null,5160,516],"date":"2020-07-05T12:57:03.790Z"}

1
text/vtx/980a Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":"980","index":"a","owner":9,"cost":0,"content":"ESxsEnxsFHwsEywXN2t/IzM3ayM1ICAgICAgICAgICAgICAgICAgIBF/axJ/ahQvfBN/FzVobyA8PSQsNSAgICAgICAgICAgICAgICAgICARLy4SLyoULC8TLxd1cHpwcXV6cDUgICAgICAgICAgICAgICAgICAgIFZpZGVvdGV4ICAgICAgICAgICAgICAgICAgICAgIA1XZWxjb21lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdlbGNvbWUsIHlvdSBoYXZlIGNvbm5lY3RlZCB0byAgICAgICAgIAFBAk4DUwRJB3RleCBhIEJCUyB0aGF0IGlzIGJhc2VkIG9uIHRoZSAgMTk4MCdzIFZpZGVvdGV4IHNlcnZpY2UuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZXBlbmRpbmcgb24gd2hpY2ggY291bnRyeSB5b3UgbGl2ZSBpbiAgdGhlIFZpZGVvdGV4IHNlcnZpY2Ugd2FzIGNhbGxlZCBWaWF0ZWwgIChBVSksIFByZXN0ZWwgKFVLKSwgTWluaXRlbCAoRlIpLCAgICAgICBUZWxpZG9uIChDQSksIEliZXJ0ZXggKFNQKSwgZXRjICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIHlvdSBnb3QgaGVyZSBieSBtaXN0YWtlLCB5b3Ugc2hvdWxkICBkaXNjb25uZWN0IG5vdywgb3RoZXJ3aXNlIHlvdSBjYW4gICAgICAgcHJlc3MCMAd0byBnZXQgdG8gdGhlIGxvZ2luIHNjcmVlbi4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUbyBsb2dpbiB1c2luZyBTUVJMLCBwcmVzcwIx","isPublic":1,"isAccessible":1,"type":"i","key":[0,982,null,null,null,null,null,null,null,null],"date":"2020-07-05T12:57:03.790Z"}

1
text/vtx/981a Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":981,"index":"a","owner":9,"cost":0,"content":"ESxsEnxsFHwsEywXN2t/IzM3ayM1ICAgICAgICAgAht5eXl5eXl5eRF/axJ/ahQvfBN/FzVobyA8PSQsNSAgICAgICAgICAgICAgICAgICARLy4SLyoULC8TLxd1cHpwcXV6cDUgICAgICAgICAgICAgICAgICAgIFZpZGVvdGV4ICAgICAgICAgICAgICAgICAgICAgDVJlZ2lzdGVyICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICACKiogB3RvIGNsZWFyIGlucHV0ICAgICAgICAgICAgICAgICAgICAgAiowMAd0byBzdGFydCBhZ2FpbiAgICAgICBVc2UCXwd0byBFbnRlciAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAXGiEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISAgAUVtYWlsICAgIDoHG2VlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWUgIAFVc2VyIElEICA6Bxtubm5ubm5ubm5ubm5ubm4gICAgICAgICAgICABUGFzc3dvcmQgOgcbcHBwcHBwcHBwcHBwcHBwICAgICAgICAgICAgAUZ1bGwgTmFtZToHG2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYgIAFUb2tlbiAgICA6Bxt6enp6enogICAgICAgICAgICAgICAgICAgICAXGjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSZWdpc3RlcmluZyBhbmQgdXNpbmcgdGhpcyBzeXN0ZW0sIHlvdSAgYWdyZWUgdG8gYWJpZGUgYnkgdGhlIHN5c3RlbSBydWxlcy4gICAgIFNlZQIqOTg4XyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAo=","isPublic":1,"isAccessible":1,"type":"r","key":[980,"register",null,null,null,null,null,null,null,null],"date":"2020-07-08T05:17:35.174Z"}

1
text/vtx/981b Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":"981","index":"b","owner":9,"cost":0,"content":"ESxsEnxsFHwsEywXN2t/IzM3ayM1ICAgICAgICAgIAIwMDEwMDEwMRF/axJ/ahQvfBN/FzVobyA8PSQsNSAgICAgICAgICAgICAgICAgICARLy4SLyoULC8TLxd1cHpwcXV6cDUgICAgICAgICAgICAgICAgICAgIFZpZGVvdGV4ICAgICAgICAgICAgICAgICAgICAgIA1XZWxjb21lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdlbGNvbWUgdG8BQQJOBFMDSQd0ZXgsIGEgYnVsbGV0aW4gICAgICBib2FyZCBzeXN0ZW0gYmFzZWQgb24gdGhlIDE5ODAncyAgICAgICAgVmlkZW90ZXggc2VydmljZS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBZb3VyIHVzYWdlIGhlcmUgaXMgZ292ZXJuZWQgYnkgc29tZSAgICAgcnVsZXMsIHdoaWNoIHlvdSBjYW4gc2VlIG9uIHBhZ2UCKjk4OF8gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiB5b3UgYXJlIG5vdCBmYW1pbGlhciB3aXRoIFZpZGVvdGV4LCAgeW91IGNhbiBmaW5kIHNvbWUgaW5mb3JtYXRpb24gb24gcGFnZSAgAio1MTZfICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVG8gZ2V0IHRvIHRoZSBob21lIHBhZ2UgYW55dGltZSwgdXNlICAgAiowXwdvciB1c2UCMAdub3cuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAo=","isPublic":1,"isAccessible":1,"type":"i","key":[0,null,null,null,null,null,null,null,null,null],"date":"2023-12-30T12:57:03.790Z"}

1
text/vtx/983a Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":983,"index":"a","owner":9,"cost":0,"content":"ESxsEnxsFHwsEywXN2t/IzM3ayM1ICAgICAgICACMDAwMTAwMDEwMRF/axJ/ahQvfBN/FzVobyA8PSQsNQcgICAgICAgICAgICAgICAgICARLy4SLyoULC8TLxd1cHpwcXV6cDUgICAgICAgICAgICAgICAgICAgIFZpZGVvdGV4ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTG9naW4BDUZBSUxFRCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUHJlc3MCMAd0byB0cnkgYWdhaW4uICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJZiB0aGlzIGtlZXBzIGhhcHBlbmluZywgeW91IG1heSBsaWtlICAgdG8gcmVxdWVzdCB0aGF0IHlvdXIgcGFzc3dvcmQgaXMgICAgICAgIHJlc2V0LiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAo=","isPublic":1,"isAccessible":1,"type":"r","key":[0,null,null,null,null,null,null,null,null,null],"frame_fields": [],"date":"2020-08-09T11:42:40.643Z"}

1
text/vtx/98a Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":98,"index":"a","owner":9,"cost":0,"content":"ESxsEnxsFHwsEywXN2t/IzM3ayM1ICAgICAgICAgIAIwMDAxMDAwMRF/axJ/ahQvfBN/FzVobyA8PSQsNQcgICAgICAgICAgICAgICAgICARLy4SLyoULC8TLxd1cHpwcXV6cDUgICAgICAgICAgICAgICAgICAgIFZpZGVvdGV4ICAgICAgICAgICAgICAgICAgICAgIA1TaWduIEluICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAVVzZXIHLi4uLi4uLi4uLi4uLi4uICAgICAgICAgICAgICAgICAgIAFQYXNzBy4uLi4uLi4uLi4uLi4uLiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFbnRlcgJORVcHdG8gcmVnaXN0ZXIgbmV3IGFjY291bnQgICAgICAgUHJlc3MCKioHdG8gY2xlYXIgeW91ciBpbnB1dCAgICAgICAgICAgIFByZXNzAiowMAd0byBzdGFydCBhZ2Fpbgo=","isPublic":1,"isAccessible":1,"type":"l","key":[null,"login",null,null,null,null,null,null,null,null],"input_fields": [{"type":"t","length":15,"char":".","name":"USER","y":10,"x":15,"attribute":{},"value":""},{"type":"p","length":15,"char":".","name":"PASS","y":11,"x":15,"attribute":{},"value":""}],"date":"2020-07-08T05:17:35.174Z"}

1
text/vtx/98b Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":98,"index":"b","owner":9,"cost":0,"content":"ESxsEnxsFHwsEywXN2t/IzM3ayM1ICAgICAgICACMDAwMTAwMDEwMRF/axJ/ahQvfBN/FzVobyA8PSQsNQcgICAgICAgICAgICAgICAgICARLy4SLyoULC8TLxd1cHpwcXV6cDUgICAgICAgICAgICAgICAgICAgIFZpZGVvdGV4ICAgICAgICAgICAgICAgICAgICAgIA1TaWduIEluICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdlbGNvbWUgdG8BQQJOA1MESQd0ZXguICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVG8gZ2V0IHRvIHRoZSBtYWluIG1lbnUsIHlvdSBjYW4gICAgICAgIHByZXNzAiowXwdhbnkgdGltZS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUHJlc3MCMAd0byBjb250aW51ZSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA==","isPublic":0,"isAccessible":1,"type":"i","key":[1,null,null,null,null,null,null,null,null,null],"date":"2020-07-08T01:30:48.608Z"}

1
text/vtx/998a Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":998,"index":"a","owner":9,"cost":0,"content":"ICARLGwSfGwUfCwTLBc3a38jMzdrIzUgICAgICACMDAwMTAwMDEwMSAgEX9rEn9qFC98E38XNWhvIDw9JCw1ByAgICAgICAgICAgICAgICAgIBEvLhIvKhQsLxMvF3VwenBxdXpwNSAgICAgICAgICAgICAgICAgICAgVmlkZW90ZXggICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBIbW0sIGFuAQ1FcnJvcgwHb2NjdXJyZWQuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElmIHRoaXMga2VlcHMgaGFwcGVuaW5nLCB5b3UgbWF5IGxpa2UgICB0byB0ZWxsIHRoZSBzeXN0ZW0gYWRtaW5pc3RyYXRvciB2aWEgICAgcGFnZQIqMDggICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUHJlc3MCMAd0byByZXR1cm4gdG8gdGhlIG1haW4gbWVudS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA==","isPublic":1,"isAccessible":1,"type":"i","key":[0,null,null,null,null,null,null,null,null,null],"frame_fields": [],"date":"2020-08-09T11:42:40.643Z"}

1
text/vtx/999a Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":999,"index":"a","owner":9,"cost":0,"content":"AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAwMRceD3MTGhYeHxgEDR0DRU5HSU5FRVJJTkcgEhwMHnMVDhEPFA8HMDIXHg9zExoWHh8YBA0dA0VOR0lORUVSSU5HIBIcDB5zFQ4RDxQPBzAyfn9+f35/fn9+f35/fn9+f35/fn9+f35/fn9+f35/fn9+f35/fn8wNBQaHnMRGRUAFQENIAUdAlRlc3QgUGFnZSAgHAweEnMWGBMAFxgBMDUUGh5zERkVABUBDSAFHQJUZXN0IFBhZ2UgIBwMHhJzFhgTABcYATA1AQABIAAgAR4gHiAXLBMTFhYSEhIVFRERFBQUICAUAAEAAQABAAEwN35/fn9+f35/fn9+f35/fn9+f35/fn9+f35/fn9+f35/fn9+f35/MDgBAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABADA5fn9+f35/fn9+f35/fn9+f35/fn9+f35/fn9+f35/fn9+f35/fn8xMAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAMTF+f35/fn9+f35/fn9+f35/fn9+f35/fn9+f35/fn9+f35/fn9+fzEyAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAxM35/fn9+f35/fn9+f35/fn9+f35/fn9+f35/fn9+f35/fn9+f35/MTQBAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABADE1fn9+f35/fn9+f35/fn9+f35/fn9+f35/fn9+f35/fn9+f35/fn8xNldoaXRlA1llbGxvdwZDeWFuAkdyZWVuBU1hZ2VudGEBUmVkBEJsdWUXGiEiIxMkJSYnFigpKisSLC0uLxkwMTIzFTQ1NjcRODk6OxQ8PT4/ICAhIiMgJCUmJyAoKSorICwtLi8gMDEyMyA0NTY3IDg5OjsgPD0+PyBAQUJDIERFRkcgSElKSyBMTU5PIFBRUlMgVFVWVyBYWVpbIFxdXl8gYGFiYyBkZWZnIGhpamsgbG1ubyBwcXJzIHR1dncgeHl6eyB8fX5/FGBhYmMRZGVmZxVoaWprEmxtbm8acHFycxZ0dXZ3E3h5ensXfH1+fwMYQ29uY2VhbAhGbGFzaAMqCwtCb3gJU3RlYWR5GEdvbmU/Fl5/","isPublic":1,"isAccessible":1,"type":"i","key":[1,null,null,null,null,null,null,null,null,null],"date":"2019-10-27T00:52:54.117Z"}

1
text/vtx/999b Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":999,"index":"b","owner":9,"cost":0,"content":"ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAINQ2hhcmFjdGVyIE1hcAwHNyBiaXQgY2hhcmFjdGVyIHNldCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICABICAwIDEgMiAzIDQgNSA2IDcgOCA5IGEgYiBjIGQgZSBmICAgICAgATAAYgFyAmcDeQRiBW0GYwd3CGYJcyBFIFMgTiBIIEQgZCAgICAgIAExEEIRUhJHE1kUQhVNFkMXVwdDIE8gbyBzIEIgYiBIIHIgICAgICABMgcgICEgIiAjICQgJSAmICcgKCApICogKyAsIC0gLiAvICAgICAgATMHMCAxIDIgMyA0IDUgNiA3IDggOSA6IDsgPCA9ID4gPyAgICAgIAE0B0AgQSBCIEMgRCBFIEYgRyBIIEkgSiBLIEwgTSBOIE8gICAgICABNQdQIFEgUiBTIFQgVSBWIFcgWCBZIFogWyBcIF0gXiBfICAgICAgATYHYCBhIGIgYyBkIGUgZiBnIGggaSBqIGsgbCBtIG4gbyAgICAgIAE3B3AgcSByIHMgdCB1IHYgdyB4IHkgeiB7IHwgfSB+IH8gICAgAiBHcmFwaGljcyBDaGFycyAgICAgICAgICAgICAgICAgICAgICAgICAaATIXICAhICIgIyAkICUgJiAnICggKSAqICsgLCAtIC4gLyAgICAgGgEzFzAgMSAyIDMgNCA1IDYgNyA4IDkgOiA7IDwgPSA+ID8gICAgIBoBNhdgIGEgYiBjIGQgZSBmIGcgaCBpIGogayBsIG0gbiBvICAgICAaATcXcCBxIHIgcyB0IHUgdiB3IHggeSB6IHsgfCB9IH4gfyAgICACDVNwZWNpYWwgQ29kZXMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICABMDgvMDkHRmxhc2gvU3RlYWR5IAExOC8xRgdDb25jZWFsL1Nob3cgATBBLzBCB0VuZC9TdGFydCBCb3gBMUMHQmxrIEJhY2sgICAgICAgIAEwQy8wRAdOb3JtYWwvRG91YmxlATFEB05ldyBCYWNrICAgICAgICABMEUvMEYHRCBXaWRlL1NpemUgIAUxOS8xQQZTb2xpZC9CbG9jayAgATFFLzFGB0hvbGQvUmVsZWFzZSBNb3NpYWMgIAUxQgZDU0kgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA==","isPublic":1,"isAccessible":1,"type":"i","key":[1,null,null,null,null,null,null,null,null,null],"date":"2019-10-27T00:52:54.117Z"}

1
text/vtx/99a Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":99,"index":"a","owner":9,"cost":0,"content":"Fzcrby8/Py9vICAgICAgICAgIBEsbBJ8bBR8LBMsFzdrfyMzN2sjNRc1amogJTVzeiAgICAgICAgICARf2sSf2oUL3wTfxc1aG8gPD0kLDUXdXJ6c3F1c3ogICAgICAgICAgES8uEi8qFCwvEy8XdXB6cHF1enA1ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFZpZGVvdGV4ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgWW91IGFyZSBhYm91dCB0bwFkaXNjb25uZWN0B2Zyb20gICAgICAgAUECTgRTA0kHdGV4LiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVG8gaGVhZCBiYWNrIHRvIHRoZSBtYWluIG1lbnUsIHBsZWFzZSAgIHVzZQIqMF8HICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT3RoZXJ3aXNlIHRvIGRpc2Nvbm5lY3QgcHJlc3MCXyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA1UaGFua3MgZm9yIGNhbGxpbmcgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA==","isPublic":1,"isAccessible":1,"type":"i","key":[0,null,null,null,null,null,null,null,null,null],"date":"2020-07-15T12:15:47.742Z"}

1
text/vtx/99b Normal file
View File

@ -0,0 +1 @@
{"version":1,"frame":99,"index":"b","owner":9,"cost":0,"content":"Fzcrby8/Py9vICAgICAgICAgIBEsbBJ8bBR8LBMsFzdrfyMzN2sjNRc1amogJTVzeiAgICAgICAgICARf2sSf2oUL3wTfxc1aG8gPD0kLDUXdXJ6c3F1c3ogICAgICAgICAgES8uEi8qFCwvEy8XdXB6cHF1enA1ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFZpZGVvdGV4ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANVGhhbmtzIGZvciBjYWxsaW5nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSG9wZSB0byBzZWUgeW91IGFnYWluLiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAo=","isPublic":1,"isAccessible":1,"type":"t","key":[1,null,null,null,null,null,null,null,null,null],"date":"2020-07-08T01:48:01.797Z"}

243
tools/export.js Normal file
View 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();

42
tools/frame_load.js Normal file
View File

@ -0,0 +1,42 @@
/**
* Load a frame, optionally with a new ANSI/BIN and load it into the msgbase.
*/
load('ansitex/load/funcs.js');
// Our page handler
load('ansitex/load/page.js');
/* parse command arguments */
if (argv.length !== 3) {
writeln('! ERROR: Need 3 arguments only');
exit(1);
}
var frame = argv.shift();
var index = argv.shift();
var file = argv.shift();
var ext = file_getext(file).substr(1).toLowerCase();
// Type of frame to load
switch (ext) {
case 'tex':
case 'ans':
require('ansitex/load/session/ansitex.js','SESSION_ANSITEX');
break;
case 'vtx':
case 'bin':
require('ansitex/load/session/viewdata.js','SESSION_VIEWDATA');
break;
}
var page = new Page();
if (page.get(new PageObject(frame,index))) {
page.import(file,ext);
page.save();
} else if (['vtx','tex'].indexOf(ext) !== -1) {
page.import(file,ext);
page.save();
}

144
tools/frames_check.js Normal file
View File

@ -0,0 +1,144 @@
/**
* This will go through our videotex and ansitex frames and check for inconsistencies.
*
* We'll check for:
* + Mismatched metadata items
* + Out of sync content
*/
load('ansitex/load/funcs.js');
// Our page handler
load('ansitex/load/page.js');
/* parse command arguments */
if (argv.length !== 1) {
writeln('! ERROR: Need only 1 argument');
exit(1);
}
//const vtx_ext = 'tex';
const vtx_src = 'bin';
const tex_src = 'ans';
const page = argv.shift();
const vtx_srcname = page+'.'+vtx_src;
const tex_srcname = page+'.'+tex_src;
var errors = false;
PAGE_FILE_PREFX = /^[0-9]+[a-z]$/;
if (! PAGE_FILE_PREFX.test(page)) {
writeln('PAGE is not a frame: '+page);
exit(2);
}
writeln('Comparing Frame: '+page);
// Load frame
require(ANSITEX_HOME+'/load/session/viewdata.js','SESSION_VIEWDATA');
vtx = new Page();
if (! vtx.import(FRAMES_HOME+SESSION_EXT+'/'+page)) {
writeln('- ! ERROR: VTX File doesnt exist? :'+page);
errors = true;
} else {
// Check content between TEX/ANS & VTX/BIN
vtx_srcfile = new File(FRAMES_HOME+SESSION_EXT+'/'+vtx_srcname);
if (! vtx_srcfile.exists || ! vtx_srcfile.open('r')) {
writeln('- ! ERROR: VTX SRC File doesnt exist? :'+vtx_srcname);
errors = true;
} else {
writeln('- LOADING: VTX Source :'+vtx_srcname);
var x = md5_calc(vtx.raw);
var y = md5_calc(vtx_srcfile.read());
// Check Content
if (x !== y) {
writeln(' - Page Content :'+x);
writeln(' - Source Content :'+y);
writeln('- ! WARNING: Content Differs.');
errors = true;
} else {
writeln('= Source matches.');
}
}
}
require(ANSITEX_HOME+'/load/session/ansitex.js','SESSION_ANSITEX');
tex = new Page();
if (! tex.import(FRAMES_HOME+SESSION_EXT+'/'+page)) {
writeln('- ! ERROR: TEX File doesnt exist? :'+page);
errors = true;
} else {
// Check content between TEX/ANS & VTX/BIN
tex_srcfile = new File(FRAMES_HOME+SESSION_EXT+'/'+tex_srcname);
if (! tex_srcfile.exists || ! tex_srcfile.open('r')) {
writeln('- ! ERROR: TEX SRC File doesnt exist? :'+tex_srcname);
errors = true;
} else {
writeln('- LOADING: TEX Source :'+tex_srcname);
var x = md5_calc(tex.raw);
var y = md5_calc(tex_srcfile.read());
if (x !== y) {
// Check Content
writeln(' - Page Content :'+x);
writeln(' - Source Content :'+y);
writeln('- ! WARNING: Content Differs.');
errors = true;
} else {
writeln('= Source matches.');
}
}
}
// Checking keys
if (vtx.raw && tex.raw) {
writeln('- Checking Page: ');
if (vtx.name.toString() !== tex.name.toString()) {
writeln(' - ! VTX: '+vtx.name.toString());
writeln(' - ! TEX: '+tex.name.toString());
} else {
writeln(' = PAGE: '+vtx.name.toString());
}
for each (var k in ['key','cost','type']) {
writeln('- Checking KEY: '+k);
if (JSON.stringify(vtx[k]) !== JSON.stringify(tex[k])) {
writeln(' - ! VTX: '+vtx[k]);
writeln(' - ! TEX: '+tex[k]);
errors = true;
} else {
writeln(' = KEY: '+vtx[k]);
}
}
for each (var k in ['isAccessible','isPublic']) {
writeln('- Checking Property: '+k);
if (JSON.stringify(vtx.__properties__[k]) !== JSON.stringify(tex.__properties__[k])) {
writeln(' - ! VTX: '+vtx.__properties__[k]);
writeln(' - ! TEX: '+tex.__properties__[k]);
errors = true;
} else {
writeln(' = KEY: '+vtx.__properties__[k]);
}
}
}
if (errors)
exit(1);
else
writeln('= OK');

19
tools/frames_list.js Normal file
View File

@ -0,0 +1,19 @@
load('load/string.js');
load('load/funcs.js');
load('ansitex/load/msgbases.js');
if (argv.length !== 1) {
writeln('ERROR: Need a msgbase page prefix');
exit(1);
}
ma = new MsgAreas();
area = ma.getArea(argv[0]);
writeln('Opening ['+argv[0]+'] - ('+area.msgbase.cfg.code+')');
writeln('- First:'+area.msgbase.first_msg);
writeln('- Last:'+area.msgbase.last_msg);
for (var x in area.headers) {
writeln(padright(area.headers[x].number,4,' ')+':'+area.headers[x].tags);
}

20
tools/frames_tag.js Normal file
View File

@ -0,0 +1,20 @@
/**
* 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
*/
load('load/string.js');
load('ansitex/load/msgbases.js');
const ma = new MsgAreas()
for (var i=0;i<ma.areas.length;i++) {
if (argv[0] && (argv[0] !== ma.areas[i].msgbase.cfg.code))
continue;
writeln('Area : '+ma.areas[i].full_name);
writeln('Total Messages : '+ma.areas[i].headers.length);
writeln('- Tagged Messages : '+ma.areas[i].list_tagged.length);
writeln('- Untagged Messages: '+ma.areas[i].list_untagged.length);
ma.areas[i].tag_msgs();
}

4
tools/msgbases_list.js Normal file
View File

@ -0,0 +1,4 @@
load('ansitex/load/msgbases.js');
var ma = new MsgAreas();
ma.list;

119
tools/save.js Normal file
View 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);
}