Compare commits
No commits in common. "master" and "ansitex" have entirely different histories.
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.idea
|
||||
.editorconfig
|
||||
*.debug
|
||||
dev/
|
@ -1,21 +0,0 @@
|
||||
image: docker:latest
|
||||
|
||||
stages:
|
||||
- build
|
||||
|
||||
variables:
|
||||
VERSION: 3.17c-${ARCH}
|
||||
CACHETAG: build-${ARCH}
|
||||
DOCKER_HOST: tcp://docker:2375
|
||||
|
||||
services:
|
||||
- docker:dind
|
||||
|
||||
before_script:
|
||||
- docker info
|
||||
- docker version
|
||||
- echo "$CI_JOB_TOKEN" | docker login -u "$CI_REGISTRY_USER" "$CI_REGISTRY" --password-stdin
|
||||
- ls -alR .
|
||||
|
||||
include: .gitlab-docker-x86_64.yml
|
||||
#include: .gitlab-docker-armv7l.yml
|
@ -1,16 +0,0 @@
|
||||
armv7l:build:
|
||||
variables:
|
||||
ARCH: armv7l
|
||||
stage: build
|
||||
image: docker:latest
|
||||
script:
|
||||
- if [ -f init ]; then chmod 500 init; fi
|
||||
- ([ -z "$REFRESH" ] && docker pull ${CI_REGISTRY_IMAGE}:${CACHETAG}) || echo "true"
|
||||
- docker build --cache-from ${CI_REGISTRY_IMAGE}:${CACHETAG} -t ${CI_REGISTRY_IMAGE}:${VERSION} -t ${CI_REGISTRY_IMAGE}:${CACHETAG} .
|
||||
- docker push ${CI_REGISTRY_IMAGE}:${VERSION}
|
||||
- docker push ${CI_REGISTRY_IMAGE}:${CACHETAG}
|
||||
tags:
|
||||
- docker
|
||||
- armv7l
|
||||
only:
|
||||
- master
|
@ -1,17 +0,0 @@
|
||||
x86_64:build:
|
||||
variables:
|
||||
ARCH: x86_64
|
||||
stage: build
|
||||
image: docker:latest
|
||||
script:
|
||||
- if [ -f init ]; then chmod 500 init; fi
|
||||
- ([ -z "$REFRESH" ] && docker pull ${CI_REGISTRY_IMAGE}:${CACHETAG}) || echo "true"
|
||||
- docker build --cache-from ${CI_REGISTRY_IMAGE}:${CACHETAG} -t ${CI_REGISTRY_IMAGE}:${VERSION} -t ${CI_REGISTRY_IMAGE}:${CACHETAG} .
|
||||
- docker push ${CI_REGISTRY_IMAGE}:${VERSION}
|
||||
- docker push ${CI_REGISTRY_IMAGE}:${CACHETAG}
|
||||
tags:
|
||||
- docker
|
||||
- x86_64
|
||||
only:
|
||||
- master
|
||||
|
66
Dockerfile
66
Dockerfile
@ -1,66 +0,0 @@
|
||||
# NAME leenooks/sbbs
|
||||
# VERSION 3.17c
|
||||
|
||||
FROM debian:stretch-slim
|
||||
|
||||
# Base utilities
|
||||
RUN apt-get update \
|
||||
&& apt-get install -yqq curl procps less zip unzip arj unrar-free lhasa arc zoo logrotate libmozjs185-1.0 cron \
|
||||
&& apt-get -y autoremove \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Add in Leenooks' apt repository
|
||||
RUN curl -s http://apt.leenooks.net/setup.sh | sh
|
||||
|
||||
# Add ZeroTier
|
||||
RUN echo "deb http://download.zerotier.com/debian/stretch stretch main" > /etc/apt/sources.list.d/zerotier.list
|
||||
|
||||
# Leenooks Utils
|
||||
RUN apt-get update \
|
||||
&& apt-get install -yqq --allow-unauthenticated makenl zerotier-one \
|
||||
&& apt-get -y autoremove \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Other Utilities
|
||||
RUN echo "deb http://ftp.au.debian.org/debian/ stretch contrib" > /etc/apt/sources.list.d/contrib.list
|
||||
RUN apt-get update \
|
||||
&& apt-get install -yqq dosemu \
|
||||
&& apt-get -y autoremove \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Build SBBS
|
||||
RUN apt-get update \
|
||||
&& apt-get install -yqq build-essential libnspr4-dev libncurses5-dev libmozjs185-dev cvs wget pkgconf \
|
||||
&& mkdir /opt/sbbs && cd /tmp \
|
||||
&& wget http://cvs.synchro.net/cgi-bin/viewcvs.cgi/*checkout*/install/GNUmakefile \
|
||||
&& make RELEASE=1 USE_DOSEMU=1 NO_X=1 JSINCLUDE=/usr/include/js JSLIB=mozjs185 SBBSDIR=/opt/sbbs install \
|
||||
&& rm -rf /opt/sbbs/3rdp /opt/sbbs/src \
|
||||
&& mv /opt/sbbs/ctrl /opt/sbbs/ctrl.orig \
|
||||
&& mkdir /opt/sbbs/nodes.orig && mv /opt/sbbs/node[1-4] /opt/sbbs/nodes.orig \
|
||||
&& ln -sf nodes/node1 /opt/sbbs/ \
|
||||
&& ln -sf nodes/node2 /opt/sbbs/ \
|
||||
&& ln -sf nodes/node3 /opt/sbbs/ \
|
||||
&& ln -sf nodes/node4 /opt/sbbs/ \
|
||||
&& find /opt/sbbs -name CVS -type d -exec rm -rf {} + \
|
||||
&& apt-get -y purge build-essential libnspr4-dev libncurses5-dev libmozjs185-dev cvs wget pkgconf \
|
||||
&& apt-get -y autoremove \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
WORKDIR /opt/sbbs
|
||||
ENV SBBSCTRL=/opt/sbbs/ctrl
|
||||
ENV SBBSEXEC=/opt/sbbs/exec
|
||||
ENV PATH=$PATH:${SBBSEXEC}
|
||||
|
||||
COPY init /sbin/init
|
||||
ENTRYPOINT [ "/sbin/init" ]
|
||||
CMD ["sbbs"]
|
||||
|
||||
VOLUME [ "/var/lib/zerotier-one" ]
|
||||
VOLUME [ "/opt/sbbs/data","/opt/sbbs/ctrl","/opt/sbbs/nodes","/opt/sbbs/fido" ]
|
||||
|
||||
# Set the default timezone for the container
|
||||
RUN ln -sf /usr/share/zoneinfo/Australia/Melbourne /etc/localtime
|
45
ctrl/videotex.ini
Normal file
45
ctrl/videotex.ini
Normal file
@ -0,0 +1,45 @@
|
||||
; Videotex Options
|
||||
|
||||
; Our key used to sign frames we send to other systems
|
||||
;gpg_key=0@videotex
|
||||
|
||||
[sqrl]
|
||||
auth_url=https://sqrl.dege.au
|
||||
auth_path=/sqrl/login
|
||||
auth_post=/api/sqrl
|
||||
|
||||
; The prefix configurations need to be kept in sync with other nodes
|
||||
; Frames with no specific owner are owned by this key
|
||||
; [prefix:x] - applies to pages begining with x
|
||||
; key= - the GPG key of the owner (for remote updates)
|
||||
; user= - the local user that can edit these frames (via *04)
|
||||
; logoans= - the service providers logo (for ANSI)
|
||||
; logovtx= - the service providers logo (for VIEWDATA)
|
||||
; code= - For Echomail, the echoarea tag
|
||||
; last_page= - For Echomail, the last page tagged
|
||||
|
||||
[prefix]
|
||||
key=0@videotex
|
||||
logoans=G1swbRtbMTszMW1BG1szMm1OG1szNG1TG1szM21JG1swOzMwOzQ3bXRleA==
|
||||
logovtx=AUECTgNTBEkHdGV4
|
||||
user=1
|
||||
|
||||
; System frames are owned by this key
|
||||
[prefix:9]
|
||||
key=0@videotex
|
||||
logoans=G1swbRtbMTszMW1BG1szMm1OG1szNG1TG1szM21JG1swOzMwOzQ3bXRleA==
|
||||
logovtx=AUECTgNTBEkHdGV4
|
||||
user=1
|
||||
|
||||
; ANSItex Help Pages
|
||||
[prefix:516]
|
||||
key=516@videotex
|
||||
logoans=G1swbRtbMTszMW1BG1szMm1OG1szNG1TG1szM21JG1swOzMwOzQ3bXRleA==
|
||||
logovtx=AUECTgNTBEkHdGV4
|
||||
user=1
|
||||
|
||||
; Private Net
|
||||
[prefix:10010]
|
||||
user=1
|
||||
logoans=UHJpdmF0ZU5ldA==
|
||||
logovtx=UHJpdmF0ZU5ldA==
|
24
init
24
init
@ -1,24 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
NAME="Synchronet BBS"
|
||||
|
||||
export PATH=$PATH:${SBBSEXEC}
|
||||
|
||||
if [ ! -e "${SBBSCTRL}/sbbs.ini" ]; then
|
||||
echo "* Installing SBBS ctrl files into ${SBBSCTRL}"
|
||||
cp -a ${SBBSCTRL}.orig/* ${SBBSCTRL}
|
||||
fi
|
||||
|
||||
if [ ! -d "${SBBSCTRL}/../nodes/node1" ]; then
|
||||
echo "* Installing SBBS nodes files into ${SBBSCTRL}/../nodes"
|
||||
cp -Ra ${SBBSCTRL}/../nodes.orig/* ${SBBSCTRL}/../nodes/
|
||||
fi
|
||||
|
||||
if [ -x /usr/sbin/zerotier-one -a -n "${ENABLE_ZT}" ]; then
|
||||
echo "* Starting ZeroTier"
|
||||
mkdir /dev/net && mknod /dev/net/tun -m 666 c 10 200
|
||||
/usr/sbin/zerotier-one -d
|
||||
fi
|
||||
|
||||
exec "$@"
|
17
install.txt
Normal file
17
install.txt
Normal file
@ -0,0 +1,17 @@
|
||||
26 May 2020
|
||||
Installation nodes.
|
||||
|
||||
1. For a new installation - make sure you have logged in first and created your sysop user.
|
||||
- No to creating guest account
|
||||
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
7
keys/genkey.txt
Normal file
@ -0,0 +1,7 @@
|
||||
Key-Type: default
|
||||
Key-Usage: encrypt,sign
|
||||
Name-Real: Your Name
|
||||
Name-Comment: Ansitex Page *???#
|
||||
Name-Email: ???@videotex
|
||||
Expire-Date: 0
|
||||
%commit
|
82
load/control/echomail.js
Normal file
82
load/control/echomail.js
Normal file
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* This control renders echomail
|
||||
*
|
||||
* The system echomail prefix is *1, with echomail pages built from the following page number:
|
||||
* zzzzEEpppp, where:
|
||||
* + zzzz is the zone, zero padded, the zone identifies the message groups
|
||||
* + EE is the echomail area, ie: the message areas
|
||||
* + pppp is the message number, identified by the tag attached to the message header
|
||||
*
|
||||
* (Tags are added to the messages via an external process.)
|
||||
*/
|
||||
|
||||
// All controls need a unique variable so that require() can know if the control has already been loaded
|
||||
var CONTROL_ECHOMAIL = '1';
|
||||
|
||||
// Optional debug message so we can see that it is loaded
|
||||
log(LOG_DEBUG,'+ Control ECHOMAIL loaded');
|
||||
|
||||
// A unique method name (same as the control name that is called as new method() on initialisation
|
||||
function echomail(session,pagenum) {
|
||||
log(LOG_DEBUG,' - Loading echomail page:'+pagenum);
|
||||
|
||||
// has this control completed
|
||||
var complete = false;
|
||||
var ready = false;
|
||||
|
||||
function init(session,pagenum) {
|
||||
log(LOG_DEBUG,' - Echomail init('+pagenum+')');
|
||||
|
||||
ready = session.loadMessage(pagenum);
|
||||
}
|
||||
|
||||
Object.defineProperty(this, 'getName', {
|
||||
get: function () {
|
||||
return 'ECHOMAIL';
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'isComplete', {
|
||||
get: function () {
|
||||
return complete;
|
||||
}
|
||||
});
|
||||
|
||||
// Handle the keyboard responses as we receive them.
|
||||
this.handle = function(read) {
|
||||
log(LOG_DEBUG,'Control ECHOMAIL handle() start. ('+read+')');
|
||||
|
||||
switch(read) {
|
||||
case KEY_DOWN:
|
||||
session.page.scroll(0,1);
|
||||
read = '';
|
||||
break;
|
||||
|
||||
case KEY_UP:
|
||||
session.page.scroll(0,-1);
|
||||
read = '';
|
||||
break;
|
||||
}
|
||||
|
||||
session.render();
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
/**
|
||||
* ready() is called after a control has been initialised, to determine if the control will take the input
|
||||
* or if it is unable to do so
|
||||
*
|
||||
* If ready() returns:
|
||||
* + false, the main programming will return ERR_ROUTE to the user,
|
||||
* + true, the main programming will continue to load the frame, and then pass input to on the next loop handle()
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
this.ready = function() {
|
||||
log(LOG_DEBUG,'echomail:ready = '+JSON.stringify(ready));
|
||||
return ready;
|
||||
}
|
||||
|
||||
init.apply(this,arguments);
|
||||
}
|
157
load/control/frameedit.js
Normal file
157
load/control/frameedit.js
Normal file
@ -0,0 +1,157 @@
|
||||
load('ansiedit.js');
|
||||
|
||||
load('frame.js');
|
||||
load('tree.js');
|
||||
load('scrollbar.js');
|
||||
load('event-timer.js');
|
||||
load('graphic.js');
|
||||
|
||||
var CONTROL_FRAMEEDIT = '1';
|
||||
|
||||
function edit(session) {
|
||||
log(LOG_DEBUG,'+ Control EDIT loaded');
|
||||
var complete = false;
|
||||
var inProperty = false;
|
||||
|
||||
function init(session) {
|
||||
log(LOG_DEBUG,' - Edit init()');
|
||||
}
|
||||
|
||||
Object.defineProperty(this,'getName', {
|
||||
get: function() {
|
||||
return 'Frame Edit';
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this,'isComplete', {
|
||||
get: function() {
|
||||
return complete;
|
||||
}
|
||||
});
|
||||
|
||||
log(LOG_DEBUG,' - Owner: '+JSON.stringify(fo.owner));
|
||||
|
||||
const frame = new Frame(1,1,console.screen_columns,console.screen_rows,BG_BLACK|LIGHTGRAY);
|
||||
frame.gotoxy(1,1);
|
||||
|
||||
header = '\1n'+fo.pageownerlogo+' '.repeat(fo.settings.FRAME_HEADER-console.strlen(fo.pageownerlogo))+'\1n '+
|
||||
'\1W\1H'+fo.page+' '.repeat(fo.settings.FRAME_PAGENUM-fo.page.length)+' '+
|
||||
'\1G\1H'+' Edit';
|
||||
frame.putmsg(header);
|
||||
frame.open();
|
||||
|
||||
var editor = new ANSIEdit({
|
||||
x: 1,
|
||||
y: 2,
|
||||
width: 80,
|
||||
height: 23,
|
||||
attr: WHITE,
|
||||
//showPosition: true,
|
||||
menuHeading: 'Frame Edit '+fo.page,
|
||||
parentFrame: frame,
|
||||
});
|
||||
|
||||
editor.open();
|
||||
editor.menu.addItem('Properties',properties);
|
||||
editor.menu.addItem('Exit',onexit);
|
||||
editor.menu.addItem('Save & Exit',saveexit);
|
||||
|
||||
var x = new Graphic;
|
||||
x.ANSI = fo.parse(base64_decode(fo.content));
|
||||
log(LOG_DEBUG,' - Fields: '+JSON.stringify(fo.frame_fields));
|
||||
|
||||
const bin = x.BIN;
|
||||
var o = 0; // offset into 'bin'
|
||||
for (var yy = 0; yy < 22; yy++) {
|
||||
for (var xx = 0; xx < 80; xx++) {
|
||||
editor.putChar({
|
||||
x : xx,
|
||||
y : yy,
|
||||
ch : bin.substr(o, 1),
|
||||
attr : bin.substr(o + 1, 1).charCodeAt(0) || BG_BLACK
|
||||
});
|
||||
o = o + 2;
|
||||
}
|
||||
}
|
||||
|
||||
editor.cycle();
|
||||
frame.cycle();
|
||||
|
||||
this.handle=function(read) {
|
||||
if (! js.terminated) {
|
||||
switch (ascii(read)) {
|
||||
case 26:
|
||||
if (inProperty) {
|
||||
propFrame.close();
|
||||
frame.top();
|
||||
frame.cycle();
|
||||
inProperty = false;
|
||||
|
||||
} else {
|
||||
properties();
|
||||
}
|
||||
|
||||
return '';
|
||||
|
||||
case 27:
|
||||
if (inProperty) {
|
||||
log(LOG_DEBUG, ' + FrameEdit properties(): ESC');
|
||||
propFrame.close();
|
||||
frame.top();
|
||||
frame.cycle();
|
||||
inProperty = false;
|
||||
}
|
||||
|
||||
return '';
|
||||
|
||||
default:
|
||||
if (inProperty) {
|
||||
log(LOG_DEBUG, ' + FrameEdit properties(): read');
|
||||
propFrame.putmsg(read);
|
||||
propFrame.cycle();
|
||||
|
||||
} else {
|
||||
editor.getcmd(read);
|
||||
editor.cycle();
|
||||
frame.cycle();
|
||||
}
|
||||
|
||||
if (! complete)
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
editor.close();
|
||||
frame.close();
|
||||
fo.render();
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function properties() {
|
||||
inProperty = true;
|
||||
log(LOG_DEBUG, '+ FrameEdit properties()');
|
||||
frame.bottom();
|
||||
propFrame = new Frame(1,2,40,14,BG_BLUE|WHITE,frame);
|
||||
propFrame.gotoxy(1,1);
|
||||
propFrame.putmsg('Properties!');
|
||||
propFrame.open();
|
||||
propFrame.cycle();
|
||||
}
|
||||
|
||||
function save() {
|
||||
fo.content = base64_encode(editor.exportAnsi().join(''));
|
||||
fo.save();
|
||||
}
|
||||
|
||||
function onexit() {
|
||||
complete = true;
|
||||
}
|
||||
|
||||
function saveexit() {
|
||||
save();
|
||||
onexit();
|
||||
}
|
||||
|
||||
init.apply(this,arguments);
|
||||
}
|
212
load/control/register.js
Normal file
212
load/control/register.js
Normal 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
205
load/control/sqrllogin.js
Normal file
@ -0,0 +1,205 @@
|
||||
/**
|
||||
* This handles user registration.
|
||||
*
|
||||
* The form must have the following fields:
|
||||
* + UID The user's user id to login
|
||||
* + EMAIL The users's email address - to receive tokens
|
||||
* + FULLNAME The user's full name
|
||||
* + PASS The users's preferred password
|
||||
* + CITY The user's city
|
||||
* + COUNTRY The user's country - 3 letter ISO code
|
||||
* + PCODE THe user's postal code
|
||||
*/
|
||||
|
||||
var CONTROL_SQRL = '1';
|
||||
|
||||
require('http.js','HTTPRequest');
|
||||
load('ansitex/load/qrcode-make.js');
|
||||
load('frame.js');
|
||||
|
||||
log(LOG_DEBUG,'+ Control SQRL-LOGIN loaded');
|
||||
|
||||
// @todo This should move to handle - since we have to press 2 twice to get out.
|
||||
function sqrllogin() {
|
||||
var complete = false;
|
||||
var cancel = false;
|
||||
|
||||
var sqrl = loadOptions('sqrl');
|
||||
log(LOG_DEBUG,'OPTIONS: '+JSON.stringify(sqrl));
|
||||
var http = new HTTPRequest();
|
||||
http.SetupGet(sqrl.auth_path,undefined,sqrl.auth_url);
|
||||
http.request_headers.push('Accept: application/json');
|
||||
|
||||
try {
|
||||
http.SendRequest();
|
||||
http.ReadResponse();
|
||||
log(LOG_INFO,'SQRL: '+JSON.stringify(http.body));
|
||||
|
||||
var data = http.body
|
||||
.split('')
|
||||
.map(function(x) {return x.charCodeAt(0)});
|
||||
var qr = qrcodegen.QrCode.encodeBinary(data,qrcodegen.QrCode.Ecc.LOW);
|
||||
|
||||
var subframe = new Frame((viewdata ? fo.settings.FRAME_WIDTH : fo.settings.FRAME_WIDTH-qr.size-2),2,(viewdata ? qr.size/2 : qr.size+2),22,BG_BLACK|LIGHTGRAY);
|
||||
fo.qrcode(qr,subframe);
|
||||
|
||||
fo.baselineSend('CANCEL_MSG',false);
|
||||
|
||||
// Loop and see if the user has logged in
|
||||
var nut = http.body.substr(http.body.indexOf('nut='),68);
|
||||
|
||||
} catch (err) {
|
||||
log(LOG_INFO,'SQRL Error: '+err+' '+JSON.stringify(sqrl));
|
||||
cancel = true;
|
||||
}
|
||||
|
||||
// Called before processing for a field
|
||||
Object.defineProperty(this, 'getName', {
|
||||
get: function () {
|
||||
return 'SQRL-LOGIN';
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'isComplete', {
|
||||
get: function () {
|
||||
return complete;
|
||||
}
|
||||
});
|
||||
|
||||
// Nothing to do here
|
||||
this.handle=function(read) {
|
||||
log(LOG_DEBUG,'Control SQRL-LOGIN handle() start. ('+read+')');
|
||||
|
||||
if (read === '2') {
|
||||
log(LOG_INFO,'SQRL: Cancelled with 2');
|
||||
cancel = true;
|
||||
|
||||
} else {
|
||||
log(LOG_DEBUG,'SQRL read ['+read+']');
|
||||
|
||||
try {
|
||||
http = new HTTPRequest();
|
||||
http.SetupGet(sqrl.auth_post+'?'+nut,undefined,sqrl.auth_url);
|
||||
http.request_headers.push('Accept: application/json');
|
||||
|
||||
log(LOG_DEBUG,'Checking NUT in ['+nut+']');
|
||||
http.SendRequest();
|
||||
http.ReadResponse();
|
||||
|
||||
switch (http.response_code) {
|
||||
case 404:
|
||||
log(LOG_DEBUG,'- NUT not Authorised yet.');
|
||||
break;
|
||||
|
||||
case 200:
|
||||
var result = JSON.parse(http.body);
|
||||
|
||||
if (result.isReady) {
|
||||
log(LOG_INFO,'NUT: '+result.msg);
|
||||
log(LOG_INFO,'NEXT: '+result.nextPage);
|
||||
if (result.msg === 'SQRL authenticated') {
|
||||
log(LOG_DEBUG,'Getting Authenticated User ['+result.nextPage+']');
|
||||
http = new HTTPRequest();
|
||||
http.SetupGet(result.nextPage,undefined,'');
|
||||
http.request_headers.push('Accept: application/json');
|
||||
|
||||
http.SendRequest();
|
||||
http.ReadResponse();
|
||||
|
||||
log(LOG_DEBUG,'Getting Authenticated User Response ['+http.response_code+']');
|
||||
if (http.response_code === 200) {
|
||||
var sqrluser = http.body.substr(0,40);
|
||||
var username = 'S'+sqrluser.substr(0,24)
|
||||
log(LOG_DEBUG,'Getting Authenticated sqrluser ['+JSON.stringify(sqrluser)+']');
|
||||
|
||||
// Look through our user base for an existing user
|
||||
var uid = system.matchuser(username);
|
||||
|
||||
log(LOG_DEBUG,'Getting Authenticated UID ['+JSON.stringify(uid)+']');
|
||||
if (! uid) {
|
||||
log(LOG_DEBUG,'New User ['+username+'] with pass ('+sqrluser+')');
|
||||
var user = system.new_user(username);
|
||||
log(LOG_DEBUG,'New User ['+JSON.stringify(user.number)+']');
|
||||
user.name = username;
|
||||
user.security.password = sqrluser;
|
||||
user.handle = username.substr(0,8);
|
||||
user.location = 'Earth';
|
||||
user.zipcode = '000';
|
||||
user.netmail = username+'@'+system.inet_addr;
|
||||
|
||||
user.comment = 'ANSItex registered user - with SQRL';
|
||||
bbs.user_sync();
|
||||
|
||||
} else {
|
||||
user = new User(uid);
|
||||
}
|
||||
|
||||
log(LOG_DEBUG,'Getting Authenticated USER ['+JSON.stringify(user.number)+']');
|
||||
|
||||
// Existing user, we'll exit here
|
||||
if (bbs.login(user.name,null,user.security.password)) {
|
||||
log(LOG_DEBUG,' - User:'+JSON.stringify(user.number));
|
||||
bbs.logon();
|
||||
log(LOG_DEBUG,' - SEND TO EXIT:');
|
||||
|
||||
complete = true;
|
||||
action = ACTION_EXIT;
|
||||
if (typeof subframe === 'object')
|
||||
subframe.close();
|
||||
|
||||
break;
|
||||
|
||||
} else {
|
||||
log(LOG_ERROR,'- Login Failed? ');
|
||||
cancel = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
log(LOG_ERROR,'- Unhandled User Details: '+http.response_code);
|
||||
cancel = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
log(LOG_ERROR,'- Unhandled isReady msg: '+result.msg);
|
||||
cancel = true;
|
||||
}
|
||||
|
||||
complete = true;
|
||||
if (typeof subframe === 'object')
|
||||
subframe.close();
|
||||
|
||||
} else {
|
||||
log(LOG_ERROR,'- Unhandled isReady: '+result.isReady);
|
||||
cancel = true;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
log(LOG_ERROR,'- Unhandled response code: '+http.response_code);
|
||||
|
||||
// We are done
|
||||
cancel = true;
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
log(LOG_INFO,'SQRL Error: '+err+' '+JSON.stringify(sqrl));
|
||||
cancel = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (cancel) {
|
||||
log(LOG_INFO,'SQRL: Processing CANCEL ['+read+'].');
|
||||
complete = true;
|
||||
if (typeof subframe === 'object')
|
||||
subframe.close();
|
||||
|
||||
action = ACTION_GOTO;
|
||||
next_page = FRAME_LOGIN;
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
}
|
||||
|
||||
this;
|
149
load/defs.js
Normal file
149
load/defs.js
Normal 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
533
load/funcs.js
Normal 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
428
load/msgbases.js
Normal 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
1386
load/page.js
Normal file
File diff suppressed because it is too large
Load Diff
995
load/qrcode-make.js
Normal file
995
load/qrcode-make.js
Normal file
@ -0,0 +1,995 @@
|
||||
'use strict';
|
||||
/*
|
||||
* Module "qrcodegen", public members:
|
||||
* - Class QrCode:
|
||||
* - Function encodeText(str text, QrCode.Ecc ecl) -> QrCode
|
||||
* - Function encodeBinary(list<byte> data, QrCode.Ecc ecl) -> QrCode
|
||||
* - Function encodeSegments(list<QrSegment> segs, QrCode.Ecc ecl,
|
||||
* int minVersion=1, int maxVersion=40, mask=-1, boostEcl=true) -> QrCode
|
||||
* - Constants int MIN_VERSION, MAX_VERSION
|
||||
* - Constructor QrCode(int version, QrCode.Ecc ecl, list<byte> dataCodewords, int mask)
|
||||
* - Fields int version, size, mask
|
||||
* - Field QrCode.Ecc errorCorrectionLevel
|
||||
* - Method getModule(int x, int y) -> bool
|
||||
* - Method drawCanvas(int scale, int border, HTMLCanvasElement canvas) -> void
|
||||
* - Method toSvgString(int border) -> str
|
||||
* - Enum Ecc:
|
||||
* - Constants LOW, MEDIUM, QUARTILE, HIGH
|
||||
* - Field int ordinal
|
||||
* - Class QrSegment:
|
||||
* - Function makeBytes(list<byte> data) -> QrSegment
|
||||
* - Function makeNumeric(str data) -> QrSegment
|
||||
* - Function makeAlphanumeric(str data) -> QrSegment
|
||||
* - Function makeSegments(str text) -> list<QrSegment>
|
||||
* - Function makeEci(int assignVal) -> QrSegment
|
||||
* - Constructor QrSegment(QrSegment.Mode mode, int numChars, list<int> bitData)
|
||||
* - Field QrSegment.Mode mode
|
||||
* - Field int numChars
|
||||
* - Method getData() -> list<int>
|
||||
* - Constants RegExp NUMERIC_REGEX, ALPHANUMERIC_REGEX
|
||||
* - Enum Mode:
|
||||
* - Constants NUMERIC, ALPHANUMERIC, BYTE, KANJI, ECI
|
||||
*/
|
||||
var qrcodegen = new function() {
|
||||
this.QrCode = function(version, errCorLvl, dataCodewords, mask) {
|
||||
|
||||
/*---- Constructor (low level) ----*/
|
||||
|
||||
// Check scalar arguments
|
||||
if (version < MIN_VERSION || version > MAX_VERSION)
|
||||
throw 'Version value out of range';
|
||||
if (mask < -1 || mask > 7)
|
||||
throw 'Mask value out of range';
|
||||
if (!(errCorLvl instanceof Ecc))
|
||||
throw 'QrCode.Ecc expected';
|
||||
var size = version * 4 + 17;
|
||||
|
||||
// Initialize both grids to be size*size arrays of Boolean false
|
||||
var row = [];
|
||||
for (var i = 0; i < size; i++)
|
||||
row.push(false);
|
||||
var modules = []; // Initially all white
|
||||
var isFunction = [];
|
||||
for (var i = 0; i < size; i++) {
|
||||
modules .push(row.slice());
|
||||
isFunction.push(row.slice());
|
||||
}
|
||||
|
||||
// Compute ECC, draw modules
|
||||
drawFunctionPatterns();
|
||||
var allCodewords = addEccAndInterleave(dataCodewords);
|
||||
drawCodewords(allCodewords);
|
||||
|
||||
// Do masking
|
||||
if (mask === -1) { // Automatically choose best mask
|
||||
var minPenalty = Infinity;
|
||||
for (var i = 0; i < 8; i++) {
|
||||
applyMask(i);
|
||||
drawFormatBits(i);
|
||||
var penalty = getPenaltyScore();
|
||||
if (penalty < minPenalty) {
|
||||
mask = i;
|
||||
minPenalty = penalty;
|
||||
}
|
||||
applyMask(i); // Undoes the mask due to XOR
|
||||
}
|
||||
}
|
||||
if (mask < 0 || mask > 7)
|
||||
throw 'Assertion error';
|
||||
applyMask(mask); // Apply the final choice of mask
|
||||
drawFormatBits(mask); // Overwrite old format bits
|
||||
|
||||
isFunction = null;
|
||||
|
||||
|
||||
/*---- Read-only instance properties ----*/
|
||||
|
||||
// The version number of this QR Code, which is between 1 and 40 (inclusive).
|
||||
// This determines the size of this barcode.
|
||||
Object.defineProperty(this, 'version', {value:version});
|
||||
|
||||
// The width and height of this QR Code, measured in modules, between
|
||||
// 21 and 177 (inclusive). This is equal to version * 4 + 17.
|
||||
Object.defineProperty(this, 'size', {value:size});
|
||||
|
||||
// The error correction level used in this QR Code.
|
||||
Object.defineProperty(this, 'errorCorrectionLevel', {value:errCorLvl});
|
||||
|
||||
// The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive).
|
||||
// Even if a QR Code is created with automatic masking requested (mask = -1),
|
||||
// the resulting object still has a mask value between 0 and 7.
|
||||
Object.defineProperty(this, 'mask', {value:mask});
|
||||
|
||||
|
||||
/*---- Accessor methods ----*/
|
||||
|
||||
// Returns the color of the module (pixel) at the given coordinates, which is false
|
||||
// for white or true for black. The top left corner has the coordinates (x=0, y=0).
|
||||
// If the given coordinates are out of bounds, then false (white) is returned.
|
||||
this.getModule = function(x, y) {
|
||||
return 0 <= x && x < size && 0 <= y && y < size && modules[y][x];
|
||||
};
|
||||
|
||||
|
||||
/*---- Public instance methods ----*/
|
||||
|
||||
// Draws this QR Code, with the given module scale and border modules, onto the given HTML
|
||||
// canvas element. The canvas's width and height is resized to (this.size + border * 2) * scale.
|
||||
// The drawn image is be purely black and white, and fully opaque.
|
||||
// The scale must be a positive integer and the border must be a non-negative integer.
|
||||
this.drawCanvas = function(scale, border, canvas) {
|
||||
if (scale <= 0 || border < 0)
|
||||
throw 'Value out of range';
|
||||
var width = (size + border * 2) * scale;
|
||||
canvas.width = width;
|
||||
canvas.height = width;
|
||||
var ctx = canvas.getContext('2d');
|
||||
for (var y = -border; y < size + border; y++) {
|
||||
for (var x = -border; x < size + border; x++) {
|
||||
ctx.fillStyle = this.getModule(x, y) ? '#000000' : '#FFFFFF';
|
||||
ctx.fillRect((x + border) * scale, (y + border) * scale, scale, scale);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Returns a string of SVG code for an image depicting this QR Code, with the given number
|
||||
// of border modules. The string always uses Unix newlines (\n), regardless of the platform.
|
||||
this.toSvgString = function(border) {
|
||||
if (border < 0)
|
||||
throw 'Border must be non-negative';
|
||||
var parts = [];
|
||||
for (var y = 0; y < size; y++) {
|
||||
for (var x = 0; x < size; x++) {
|
||||
if (this.getModule(x, y))
|
||||
parts.push('M' + (x + border) + ',' + (y + border) + 'h1v1h-1z');
|
||||
}
|
||||
}
|
||||
return '<?xml version="1.0" encoding="UTF-8"?>\n' +
|
||||
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 ' +
|
||||
(size + border * 2) + ' ' + (size + border * 2) + '" stroke="none">\n' +
|
||||
'\t<rect width="100%" height="100%" fill="#FFFFFF"/>\n' +
|
||||
'\t<path d="' + parts.join(" ") + '" fill="#000000"/>\n' +
|
||||
'</svg>\n';
|
||||
};
|
||||
|
||||
|
||||
/*---- Private helper methods for constructor: Drawing function modules ----*/
|
||||
|
||||
// Reads this object's version field, and draws and marks all function modules.
|
||||
function drawFunctionPatterns() {
|
||||
// Draw horizontal and vertical timing patterns
|
||||
for (var i = 0; i < size; i++) {
|
||||
setFunctionModule(6, i, i % 2 === 0);
|
||||
setFunctionModule(i, 6, i % 2 === 0);
|
||||
}
|
||||
|
||||
// Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)
|
||||
drawFinderPattern(3, 3);
|
||||
drawFinderPattern(size - 4, 3);
|
||||
drawFinderPattern(3, size - 4);
|
||||
|
||||
// Draw numerous alignment patterns
|
||||
var alignPatPos = getAlignmentPatternPositions();
|
||||
var numAlign = alignPatPos.length;
|
||||
for (var i = 0; i < numAlign; i++) {
|
||||
for (var j = 0; j < numAlign; j++) {
|
||||
// Don't draw on the three finder corners
|
||||
if (!(i === 0 && j === 0 || i === 0 && j === numAlign - 1 || i === numAlign - 1 && j === 0))
|
||||
drawAlignmentPattern(alignPatPos[i], alignPatPos[j]);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw configuration data
|
||||
drawFormatBits(0); // Dummy mask value; overwritten later in the constructor
|
||||
drawVersion();
|
||||
}
|
||||
|
||||
|
||||
// Draws two copies of the format bits (with its own error correction code)
|
||||
// based on the given mask and this object's error correction level field.
|
||||
function drawFormatBits(mask) {
|
||||
// Calculate error correction code and pack bits
|
||||
var data = errCorLvl.formatBits << 3 | mask; // errCorrLvl is uint2, mask is uint3
|
||||
var rem = data;
|
||||
for (var i = 0; i < 10; i++)
|
||||
rem = (rem << 1) ^ ((rem >>> 9) * 0x537);
|
||||
var bits = (data << 10 | rem) ^ 0x5412; // uint15
|
||||
if (bits >>> 15 !== 0)
|
||||
throw 'Assertion error';
|
||||
|
||||
// Draw first copy
|
||||
for (var i = 0; i <= 5; i++)
|
||||
setFunctionModule(8, i, getBit(bits, i));
|
||||
setFunctionModule(8, 7, getBit(bits, 6));
|
||||
setFunctionModule(8, 8, getBit(bits, 7));
|
||||
setFunctionModule(7, 8, getBit(bits, 8));
|
||||
for (var i = 9; i < 15; i++)
|
||||
setFunctionModule(14 - i, 8, getBit(bits, i));
|
||||
|
||||
// Draw second copy
|
||||
for (var i = 0; i < 8; i++)
|
||||
setFunctionModule(size - 1 - i, 8, getBit(bits, i));
|
||||
for (var i = 8; i < 15; i++)
|
||||
setFunctionModule(8, size - 15 + i, getBit(bits, i));
|
||||
setFunctionModule(8, size - 8, true); // Always black
|
||||
}
|
||||
|
||||
|
||||
// Draws two copies of the version bits (with its own error correction code),
|
||||
// based on this object's version field, iff 7 <= version <= 40.
|
||||
function drawVersion() {
|
||||
if (version < 7)
|
||||
return;
|
||||
|
||||
// Calculate error correction code and pack bits
|
||||
var rem = version; // version is uint6, in the range [7, 40]
|
||||
for (var i = 0; i < 12; i++)
|
||||
rem = (rem << 1) ^ ((rem >>> 11) * 0x1F25);
|
||||
var bits = version << 12 | rem; // uint18
|
||||
if (bits >>> 18 !== 0)
|
||||
throw 'Assertion error';
|
||||
|
||||
// Draw two copies
|
||||
for (var i = 0; i < 18; i++) {
|
||||
var bit = getBit(bits, i);
|
||||
var a = size - 11 + i % 3;
|
||||
var b = Math.floor(i / 3);
|
||||
setFunctionModule(a, b, bit);
|
||||
setFunctionModule(b, a, bit);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draws a 9*9 finder pattern including the border separator,
|
||||
// with the center module at (x, y). Modules can be out of bounds.
|
||||
function drawFinderPattern(x, y) {
|
||||
for (var dy = -4; dy <= 4; dy++) {
|
||||
for (var dx = -4; dx <= 4; dx++) {
|
||||
var dist = Math.max(Math.abs(dx), Math.abs(dy)); // Chebyshev/infinity norm
|
||||
var xx = x + dx, yy = y + dy;
|
||||
if (0 <= xx && xx < size && 0 <= yy && yy < size)
|
||||
setFunctionModule(xx, yy, dist !== 2 && dist !== 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draws a 5*5 alignment pattern, with the center module
|
||||
// at (x, y). All modules must be in bounds.
|
||||
function drawAlignmentPattern(x, y) {
|
||||
for (var dy = -2; dy <= 2; dy++) {
|
||||
for (var dx = -2; dx <= 2; dx++)
|
||||
setFunctionModule(x + dx, y + dy, Math.max(Math.abs(dx), Math.abs(dy)) !== 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Sets the color of a module and marks it as a function module.
|
||||
// Only used by the constructor. Coordinates must be in bounds.
|
||||
function setFunctionModule(x, y, isBlack) {
|
||||
modules[y][x] = isBlack;
|
||||
isFunction[y][x] = true;
|
||||
}
|
||||
|
||||
|
||||
/*---- Private helper methods for constructor: Codewords and masking ----*/
|
||||
|
||||
// Returns a new byte string representing the given data with the appropriate error correction
|
||||
// codewords appended to it, based on this object's version and error correction level.
|
||||
function addEccAndInterleave(data) {
|
||||
if (data.length !== QrCode.getNumDataCodewords(version, errCorLvl))
|
||||
throw 'Invalid argument';
|
||||
|
||||
// Calculate parameter numbers
|
||||
var numBlocks = QrCode.NUM_ERROR_CORRECTION_BLOCKS[errCorLvl.ordinal][version];
|
||||
var blockEccLen = QrCode.ECC_CODEWORDS_PER_BLOCK [errCorLvl.ordinal][version];
|
||||
var rawCodewords = Math.floor(QrCode.getNumRawDataModules(version) / 8);
|
||||
var numShortBlocks = numBlocks - rawCodewords % numBlocks;
|
||||
var shortBlockLen = Math.floor(rawCodewords / numBlocks);
|
||||
|
||||
// Split data into blocks and append ECC to each block
|
||||
var blocks = [];
|
||||
var rs = new ReedSolomonGenerator(blockEccLen);
|
||||
for (var i = 0, k = 0; i < numBlocks; i++) {
|
||||
var dat = data.slice(k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1));
|
||||
k += dat.length;
|
||||
var ecc = rs.getRemainder(dat);
|
||||
if (i < numShortBlocks)
|
||||
dat.push(0);
|
||||
blocks.push(dat.concat(ecc));
|
||||
}
|
||||
|
||||
// Interleave (not concatenate) the bytes from every block into a single sequence
|
||||
var result = [];
|
||||
for (var i = 0; i < blocks[0].length; i++) {
|
||||
for (var j = 0; j < blocks.length; j++) {
|
||||
// Skip the padding byte in short blocks
|
||||
if (i !== shortBlockLen - blockEccLen || j >= numShortBlocks)
|
||||
result.push(blocks[j][i]);
|
||||
}
|
||||
}
|
||||
if (result.length !== rawCodewords)
|
||||
throw 'Assertion error';
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire
|
||||
// data area of this QR Code. Function modules need to be marked off before this is called.
|
||||
function drawCodewords(data) {
|
||||
if (data.length !== Math.floor(QrCode.getNumRawDataModules(version) / 8))
|
||||
throw 'Invalid argument';
|
||||
var i = 0; // Bit index into the data
|
||||
// Do the funny zigzag scan
|
||||
for (var right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair
|
||||
if (right === 6)
|
||||
right = 5;
|
||||
for (var vert = 0; vert < size; vert++) { // Vertical counter
|
||||
for (var j = 0; j < 2; j++) {
|
||||
var x = right - j; // Actual x coordinate
|
||||
var upward = ((right + 1) & 2) === 0;
|
||||
var y = upward ? size - 1 - vert : vert; // Actual y coordinate
|
||||
if (!isFunction[y][x] && i < data.length * 8) {
|
||||
modules[y][x] = getBit(data[i >>> 3], 7 - (i & 7));
|
||||
i++;
|
||||
}
|
||||
// If this QR Code has any remainder bits (0 to 7), they were assigned as
|
||||
// 0/false/white by the constructor and are left unchanged by this method
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i !== data.length * 8)
|
||||
throw 'Assertion error';
|
||||
}
|
||||
|
||||
|
||||
// XORs the codeword modules in this QR Code with the given mask pattern.
|
||||
// The function modules must be marked and the codeword bits must be drawn
|
||||
// before masking. Due to the arithmetic of XOR, calling applyMask() with
|
||||
// the same mask value a second time will undo the mask. A final well-formed
|
||||
// QR Code needs exactly one (not zero, two, etc.) mask applied.
|
||||
function applyMask(mask) {
|
||||
if (mask < 0 || mask > 7)
|
||||
throw 'Mask value out of range';
|
||||
for (var y = 0; y < size; y++) {
|
||||
for (var x = 0; x < size; x++) {
|
||||
var invert;
|
||||
switch (mask) {
|
||||
case 0: invert = (x + y) % 2 === 0; break;
|
||||
case 1: invert = y % 2 === 0; break;
|
||||
case 2: invert = x % 3 === 0; break;
|
||||
case 3: invert = (x + y) % 3 === 0; break;
|
||||
case 4: invert = (Math.floor(x / 3) + Math.floor(y / 2)) % 2 === 0; break;
|
||||
case 5: invert = x * y % 2 + x * y % 3 === 0; break;
|
||||
case 6: invert = (x * y % 2 + x * y % 3) % 2 === 0; break;
|
||||
case 7: invert = ((x + y) % 2 + x * y % 3) % 2 === 0; break;
|
||||
default: throw 'Assertion error';
|
||||
}
|
||||
if (!isFunction[y][x] && invert)
|
||||
modules[y][x] = !modules[y][x];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Calculates and returns the penalty score based on state of this QR Code's current modules.
|
||||
// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.
|
||||
function getPenaltyScore() {
|
||||
var result = 0;
|
||||
|
||||
// Adjacent modules in row having same color, and finder-like patterns
|
||||
for (var y = 0; y < size; y++) {
|
||||
var runHistory = [0,0,0,0,0,0,0];
|
||||
var color = false;
|
||||
var runX = 0;
|
||||
for (var x = 0; x < size; x++) {
|
||||
if (modules[y][x] === color) {
|
||||
runX++;
|
||||
if (runX === 5)
|
||||
result += QrCode.PENALTY_N1;
|
||||
else if (runX > 5)
|
||||
result++;
|
||||
} else {
|
||||
QrCode.addRunToHistory(runX, runHistory);
|
||||
if (!color && QrCode.hasFinderLikePattern(runHistory))
|
||||
result += QrCode.PENALTY_N3;
|
||||
color = modules[y][x];
|
||||
runX = 1;
|
||||
}
|
||||
}
|
||||
QrCode.addRunToHistory(runX, runHistory);
|
||||
if (color)
|
||||
QrCode.addRunToHistory(0, runHistory); // Dummy run of white
|
||||
if (QrCode.hasFinderLikePattern(runHistory))
|
||||
result += QrCode.PENALTY_N3;
|
||||
}
|
||||
// Adjacent modules in column having same color, and finder-like patterns
|
||||
for (var x = 0; x < size; x++) {
|
||||
var runHistory = [0,0,0,0,0,0,0];
|
||||
var color = false;
|
||||
var runY = 0;
|
||||
for (var y = 0; y < size; y++) {
|
||||
if (modules[y][x] === color) {
|
||||
runY++;
|
||||
if (runY === 5)
|
||||
result += QrCode.PENALTY_N1;
|
||||
else if (runY > 5)
|
||||
result++;
|
||||
} else {
|
||||
QrCode.addRunToHistory(runY, runHistory);
|
||||
if (!color && QrCode.hasFinderLikePattern(runHistory))
|
||||
result += QrCode.PENALTY_N3;
|
||||
color = modules[y][x];
|
||||
runY = 1;
|
||||
}
|
||||
}
|
||||
QrCode.addRunToHistory(runY, runHistory);
|
||||
if (color)
|
||||
QrCode.addRunToHistory(0, runHistory); // Dummy run of white
|
||||
if (QrCode.hasFinderLikePattern(runHistory))
|
||||
result += QrCode.PENALTY_N3;
|
||||
}
|
||||
|
||||
// 2*2 blocks of modules having same color
|
||||
for (var y = 0; y < size - 1; y++) {
|
||||
for (var x = 0; x < size - 1; x++) {
|
||||
var color = modules[y][x];
|
||||
if (color === modules[y][x + 1] && color === modules[y + 1][x] && color === modules[y + 1][x + 1])
|
||||
result += QrCode.PENALTY_N2;
|
||||
}
|
||||
}
|
||||
|
||||
// Balance of black and white modules
|
||||
var black = 0;
|
||||
modules.forEach(function(row) {
|
||||
row.forEach(function(color) {
|
||||
if (color)
|
||||
black++;
|
||||
});
|
||||
});
|
||||
var total = size * size; // Note that size is odd, so black/total !== 1/2
|
||||
// Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)%
|
||||
var k = Math.ceil(Math.abs(black * 20 - total * 10) / total) - 1;
|
||||
result += k * QrCode.PENALTY_N4;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Returns an ascending list of positions of alignment patterns for this version number.
|
||||
// Each position is in the range [0,177), and are used on both the x and y axes.
|
||||
// This could be implemented as lookup table of 40 variable-length lists of integers.
|
||||
function getAlignmentPatternPositions() {
|
||||
if (version === 1)
|
||||
return [];
|
||||
else {
|
||||
var numAlign = Math.floor(version / 7) + 2;
|
||||
var step = (version === 32) ? 26 :
|
||||
Math.ceil((size - 13) / (numAlign*2 - 2)) * 2;
|
||||
var result = [6];
|
||||
for (var pos = size - 7; result.length < numAlign; pos -= step)
|
||||
result.splice(1, 0, pos);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Returns true iff the i'th bit of x is set to 1.
|
||||
function getBit(x, i) {
|
||||
return ((x >>> i) & 1) !== 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*---- Static factory functions (high level) for QrCode ----*/
|
||||
|
||||
/*
|
||||
* Returns a QR Code representing the given Unicode text string at the given error correction level.
|
||||
* As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer
|
||||
* Unicode code points (not UTF-16 code units) if the low error correction level is used. The smallest possible
|
||||
* QR Code version is automatically chosen for the output. The ECC level of the result may be higher than the
|
||||
* ecl argument if it can be done without increasing the version.
|
||||
*/
|
||||
this.QrCode.encodeText = function(text, ecl) {
|
||||
var segs = qrcodegen.QrSegment.makeSegments(text);
|
||||
return this.encodeSegments(segs, ecl);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Returns a QR Code representing the given binary data at the given error correction level.
|
||||
* This function always encodes using the binary segment mode, not any text mode. The maximum number of
|
||||
* bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output.
|
||||
* The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version.
|
||||
*/
|
||||
this.QrCode.encodeBinary = function(data, ecl) {
|
||||
var seg = qrcodegen.QrSegment.makeBytes(data);
|
||||
return this.encodeSegments([seg], ecl);
|
||||
};
|
||||
|
||||
|
||||
/*---- Static factory functions (mid level) for QrCode ----*/
|
||||
|
||||
/*
|
||||
* Returns a QR Code representing the given segments with the given encoding parameters.
|
||||
* The smallest possible QR Code version within the given range is automatically
|
||||
* chosen for the output. Iff boostEcl is true, then the ECC level of the result
|
||||
* may be higher than the ecl argument if it can be done without increasing the
|
||||
* version. The mask number is either between 0 to 7 (inclusive) to force that
|
||||
* mask, or -1 to automatically choose an appropriate mask (which may be slow).
|
||||
* This function allows the user to create a custom sequence of segments that switches
|
||||
* between modes (such as alphanumeric and byte) to encode text in less space.
|
||||
* This is a mid-level API; the high-level API is encodeText() and encodeBinary().
|
||||
*/
|
||||
this.QrCode.encodeSegments = function(segs, ecl, minVersion, maxVersion, mask, boostEcl) {
|
||||
if (minVersion === undefined) minVersion = MIN_VERSION;
|
||||
if (maxVersion === undefined) maxVersion = MAX_VERSION;
|
||||
if (mask === undefined) mask = -1;
|
||||
if (boostEcl === undefined) boostEcl = true;
|
||||
if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7)
|
||||
throw 'Invalid value';
|
||||
|
||||
// Find the minimal version number to use
|
||||
var version, dataUsedBits;
|
||||
for (version = minVersion; ; version++) {
|
||||
var dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8; // Number of data bits available
|
||||
dataUsedBits = qrcodegen.QrSegment.getTotalBits(segs, version);
|
||||
if (dataUsedBits <= dataCapacityBits)
|
||||
break; // This version number is found to be suitable
|
||||
if (version >= maxVersion) // All versions in the range could not fit the given data
|
||||
throw 'Data too long';
|
||||
}
|
||||
|
||||
// Increase the error correction level while the data still fits in the current version number
|
||||
[this.Ecc.MEDIUM, this.Ecc.QUARTILE, this.Ecc.HIGH].forEach(function(newEcl) { // From low to high
|
||||
if (boostEcl && dataUsedBits <= QrCode.getNumDataCodewords(version, newEcl) * 8)
|
||||
ecl = newEcl;
|
||||
});
|
||||
|
||||
// Concatenate all segments to create the data bit string
|
||||
var bb = new BitBuffer();
|
||||
segs.forEach(function(seg) {
|
||||
bb.appendBits(seg.mode.modeBits, 4);
|
||||
bb.appendBits(seg.numChars, seg.mode.numCharCountBits(version));
|
||||
seg.getData().forEach(function(bit) {
|
||||
bb.push(bit);
|
||||
});
|
||||
});
|
||||
if (bb.length !== dataUsedBits)
|
||||
throw 'Assertion error';
|
||||
|
||||
// Add terminator and pad up to a byte if applicable
|
||||
var dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8;
|
||||
if (bb.length > dataCapacityBits)
|
||||
throw 'Assertion error';
|
||||
bb.appendBits(0, Math.min(4, dataCapacityBits - bb.length));
|
||||
bb.appendBits(0, (8 - bb.length % 8) % 8);
|
||||
if (bb.length % 8 !== 0)
|
||||
throw 'Assertion error';
|
||||
|
||||
// Pad with alternating bytes until data capacity is reached
|
||||
for (var padByte = 0xEC; bb.length < dataCapacityBits; padByte ^= 0xEC ^ 0x11)
|
||||
bb.appendBits(padByte, 8);
|
||||
|
||||
// Pack bits into bytes in big endian
|
||||
var dataCodewords = [];
|
||||
while (dataCodewords.length * 8 < bb.length)
|
||||
dataCodewords.push(0);
|
||||
bb.forEach(function(bit, i) {
|
||||
dataCodewords[i >>> 3] |= bit << (7 - (i & 7));
|
||||
});
|
||||
|
||||
// Create the QR Code object
|
||||
return new this(version, ecl, dataCodewords, mask);
|
||||
};
|
||||
|
||||
|
||||
/*---- Private static helper functions for QrCode ----*/
|
||||
|
||||
var QrCode = {}; // Private object to assign properties to. Not the same object as 'this.QrCode'.
|
||||
|
||||
|
||||
// Returns the number of data bits that can be stored in a QR Code of the given version number, after
|
||||
// all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8.
|
||||
// The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table.
|
||||
QrCode.getNumRawDataModules = function(ver) {
|
||||
if (ver < MIN_VERSION || ver > MAX_VERSION)
|
||||
throw 'Version number out of range';
|
||||
var result = (16 * ver + 128) * ver + 64;
|
||||
if (ver >= 2) {
|
||||
var numAlign = Math.floor(ver / 7) + 2;
|
||||
result -= (25 * numAlign - 10) * numAlign - 55;
|
||||
if (ver >= 7)
|
||||
result -= 36;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// Returns the number of 8-bit data (i.e. not error correction) codewords contained in any
|
||||
// QR Code of the given version number and error correction level, with remainder bits discarded.
|
||||
// This stateless pure function could be implemented as a (40*4)-cell lookup table.
|
||||
QrCode.getNumDataCodewords = function(ver, ecl) {
|
||||
return Math.floor(QrCode.getNumRawDataModules(ver) / 8) -
|
||||
QrCode.ECC_CODEWORDS_PER_BLOCK [ecl.ordinal][ver] *
|
||||
QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver];
|
||||
};
|
||||
|
||||
|
||||
// Inserts the given value to the front of the given array, which shifts over the
|
||||
// existing values and deletes the last value. A helper function for getPenaltyScore().
|
||||
QrCode.addRunToHistory = function(run, history) {
|
||||
history.pop();
|
||||
history.unshift(run);
|
||||
};
|
||||
|
||||
|
||||
// Tests whether the given run history has the pattern of ratio 1:1:3:1:1 in the middle, and
|
||||
// surrounded by at least 4 on either or both ends. A helper function for getPenaltyScore().
|
||||
// Must only be called immediately after a run of white modules has ended.
|
||||
QrCode.hasFinderLikePattern = function(runHistory) {
|
||||
var n = runHistory[1];
|
||||
return n > 0 && runHistory[2] === n && runHistory[4] === n && runHistory[5] === n
|
||||
&& runHistory[3] === n * 3 && Math.max(runHistory[0], runHistory[6]) >= n * 4;
|
||||
};
|
||||
|
||||
|
||||
/*---- Constants and tables for QrCode ----*/
|
||||
|
||||
var MIN_VERSION = 1; // The minimum version number supported in the QR Code Model 2 standard
|
||||
var MAX_VERSION = 40; // The maximum version number supported in the QR Code Model 2 standard
|
||||
Object.defineProperty(this.QrCode, 'MIN_VERSION', {value:MIN_VERSION});
|
||||
Object.defineProperty(this.QrCode, 'MAX_VERSION', {value:MAX_VERSION});
|
||||
|
||||
// For use in getPenaltyScore(), when evaluating which mask is best.
|
||||
QrCode.PENALTY_N1 = 3;
|
||||
QrCode.PENALTY_N2 = 3;
|
||||
QrCode.PENALTY_N3 = 40;
|
||||
QrCode.PENALTY_N4 = 10;
|
||||
|
||||
QrCode.ECC_CODEWORDS_PER_BLOCK = [
|
||||
// Version: (note that index 0 is for padding, and is set to an illegal value)
|
||||
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
|
||||
[null, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], // Low
|
||||
[null, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28], // Medium
|
||||
[null, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], // Quartile
|
||||
[null, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], // High
|
||||
];
|
||||
|
||||
QrCode.NUM_ERROR_CORRECTION_BLOCKS = [
|
||||
// Version: (note that index 0 is for padding, and is set to an illegal value)
|
||||
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
|
||||
[null, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25], // Low
|
||||
[null, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49], // Medium
|
||||
[null, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68], // Quartile
|
||||
[null, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81], // High
|
||||
];
|
||||
|
||||
|
||||
/*---- Public helper enumeration ----*/
|
||||
|
||||
/*
|
||||
* The error correction level in a QR Code symbol. Immutable.
|
||||
*/
|
||||
this.QrCode.Ecc = {
|
||||
LOW : new Ecc(0, 1), // The QR Code can tolerate about 7% erroneous codewords
|
||||
MEDIUM : new Ecc(1, 0), // The QR Code can tolerate about 15% erroneous codewords
|
||||
QUARTILE: new Ecc(2, 3), // The QR Code can tolerate about 25% erroneous codewords
|
||||
HIGH : new Ecc(3, 2), // The QR Code can tolerate about 30% erroneous codewords
|
||||
};
|
||||
|
||||
|
||||
// Private constructor.
|
||||
function Ecc(ord, fb) {
|
||||
// (Public) In the range 0 to 3 (unsigned 2-bit integer)
|
||||
Object.defineProperty(this, 'ordinal', {value:ord});
|
||||
|
||||
// (Package-private) In the range 0 to 3 (unsigned 2-bit integer)
|
||||
Object.defineProperty(this, 'formatBits', {value:fb});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*---- Data segment class ----*/
|
||||
|
||||
/*
|
||||
* A segment of character/binary/control data in a QR Code symbol.
|
||||
* Instances of this class are immutable.
|
||||
* The mid-level way to create a segment is to take the payload data
|
||||
* and call a static factory function such as QrSegment.makeNumeric().
|
||||
* The low-level way to create a segment is to custom-make the bit buffer
|
||||
* and call the QrSegment() constructor with appropriate values.
|
||||
* This segment class imposes no length restrictions, but QR Codes have restrictions.
|
||||
* Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.
|
||||
* Any segment longer than this is meaningless for the purpose of generating QR Codes.
|
||||
* This constructor creates a QR Code segment with the given attributes and data.
|
||||
* The character count (numChars) must agree with the mode and the bit buffer length,
|
||||
* but the constraint isn't checked. The given bit buffer is cloned and stored.
|
||||
*/
|
||||
this.QrSegment = function(mode, numChars, bitData) {
|
||||
/*---- Constructor (low level) ----*/
|
||||
if (numChars < 0 || !(mode instanceof Mode))
|
||||
throw 'Invalid argument';
|
||||
|
||||
// The data bits of this segment. Accessed through getData().
|
||||
bitData = bitData.slice(); // Make defensive copy
|
||||
|
||||
// The mode indicator of this segment.
|
||||
Object.defineProperty(this, 'mode', {value:mode});
|
||||
|
||||
// The length of this segment's unencoded data. Measured in characters for
|
||||
// numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.
|
||||
// Always zero or positive. Not the same as the data's bit length.
|
||||
Object.defineProperty(this, 'numChars', {value:numChars});
|
||||
|
||||
// Returns a new copy of the data bits of this segment.
|
||||
this.getData = function() {
|
||||
return bitData.slice(); // Make defensive copy
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/*---- Static factory functions (mid level) for QrSegment ----*/
|
||||
|
||||
/*
|
||||
* Returns a segment representing the given binary data encoded in
|
||||
* byte mode. All input byte arrays are acceptable. Any text string
|
||||
* can be converted to UTF-8 bytes and encoded as a byte mode segment.
|
||||
*/
|
||||
this.QrSegment.makeBytes = function(data) {
|
||||
var bb = new BitBuffer();
|
||||
data.forEach(function(b) {
|
||||
bb.appendBits(b, 8);
|
||||
});
|
||||
return new this(this.Mode.BYTE, data.length, bb);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing the given string of decimal digits encoded in numeric mode.
|
||||
*/
|
||||
this.QrSegment.makeNumeric = function(digits) {
|
||||
if (!this.NUMERIC_REGEX.test(digits))
|
||||
throw 'String contains non-numeric characters';
|
||||
var bb = new BitBuffer();
|
||||
for (var i = 0; i < digits.length; ) { // Consume up to 3 digits per iteration
|
||||
var n = Math.min(digits.length - i, 3);
|
||||
bb.appendBits(parseInt(digits.substring(i, i + n), 10), n * 3 + 1);
|
||||
i += n;
|
||||
}
|
||||
return new this(this.Mode.NUMERIC, digits.length, bb);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing the given text string encoded in alphanumeric mode.
|
||||
* The characters allowed are: 0 to 9, A to Z (uppercase only), space,
|
||||
* dollar, percent, asterisk, plus, hyphen, period, slash, colon.
|
||||
*/
|
||||
this.QrSegment.makeAlphanumeric = function(text) {
|
||||
if (!this.ALPHANUMERIC_REGEX.test(text))
|
||||
throw 'String contains unencodable characters in alphanumeric mode';
|
||||
var bb = new BitBuffer();
|
||||
var i;
|
||||
for (i = 0; i + 2 <= text.length; i += 2) { // Process groups of 2
|
||||
var temp = QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)) * 45;
|
||||
temp += QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i + 1));
|
||||
bb.appendBits(temp, 11);
|
||||
}
|
||||
if (i < text.length) // 1 character remaining
|
||||
bb.appendBits(QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)), 6);
|
||||
return new this(this.Mode.ALPHANUMERIC, text.length, bb);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Returns a new mutable list of zero or more segments to represent the given Unicode text string.
|
||||
* The result may use various segment modes and switch modes to optimize the length of the bit stream.
|
||||
*/
|
||||
this.QrSegment.makeSegments = function(text) {
|
||||
// Select the most efficient segment encoding automatically
|
||||
if (text === '')
|
||||
return [];
|
||||
else if (this.NUMERIC_REGEX.test(text))
|
||||
return [this.makeNumeric(text)];
|
||||
else if (this.ALPHANUMERIC_REGEX.test(text))
|
||||
return [this.makeAlphanumeric(text)];
|
||||
else
|
||||
return [this.makeBytes(toUtf8ByteArray(text))];
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing an Extended Channel Interpretation
|
||||
* (ECI) designator with the given assignment value.
|
||||
*/
|
||||
this.QrSegment.makeEci = function(assignVal) {
|
||||
var bb = new BitBuffer();
|
||||
if (assignVal < 0)
|
||||
throw 'ECI assignment value out of range';
|
||||
else if (assignVal < (1 << 7))
|
||||
bb.appendBits(assignVal, 8);
|
||||
else if (assignVal < (1 << 14)) {
|
||||
bb.appendBits(2, 2);
|
||||
bb.appendBits(assignVal, 14);
|
||||
} else if (assignVal < 1000000) {
|
||||
bb.appendBits(6, 3);
|
||||
bb.appendBits(assignVal, 21);
|
||||
} else
|
||||
throw 'ECI assignment value out of range';
|
||||
return new this(this.Mode.ECI, 0, bb);
|
||||
};
|
||||
|
||||
|
||||
// (Package-private) Calculates and returns the number of bits needed to encode the given segments at the
|
||||
// given version. The result is infinity if a segment has too many characters to fit its length field.
|
||||
this.QrSegment.getTotalBits = function(segs, version) {
|
||||
var result = 0;
|
||||
for (var i = 0; i < segs.length; i++) {
|
||||
var seg = segs[i];
|
||||
var ccbits = seg.mode.numCharCountBits(version);
|
||||
if (seg.numChars >= (1 << ccbits))
|
||||
return Infinity; // The segment's length doesn't fit the field's bit width
|
||||
result += 4 + ccbits + seg.getData().length;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/*---- Constants for QrSegment ----*/
|
||||
|
||||
var QrSegment = {}; // Private object to assign properties to. Not the same object as 'this.QrSegment'.
|
||||
|
||||
// (Public) Describes precisely all strings that are encodable in numeric mode.
|
||||
// To test whether a string s is encodable: var ok = NUMERIC_REGEX.test(s);
|
||||
// A string is encodable iff each character is in the range 0 to 9.
|
||||
this.QrSegment.NUMERIC_REGEX = /^[0-9]*$/;
|
||||
|
||||
// (Public) Describes precisely all strings that are encodable in alphanumeric mode.
|
||||
// To test whether a string s is encodable: var ok = ALPHANUMERIC_REGEX.test(s);
|
||||
// A string is encodable iff each character is in the following set: 0 to 9, A to Z
|
||||
// (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.
|
||||
this.QrSegment.ALPHANUMERIC_REGEX = /^[A-Z0-9 $%*+.\/:-]*$/;
|
||||
|
||||
// (Private) The set of all legal characters in alphanumeric mode,
|
||||
// where each character value maps to the index in the string.
|
||||
QrSegment.ALPHANUMERIC_CHARSET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
|
||||
|
||||
|
||||
/*---- Public helper enumeration ----*/
|
||||
|
||||
/*
|
||||
* Describes how a segment's data bits are interpreted. Immutable.
|
||||
*/
|
||||
this.QrSegment.Mode = { // Constants
|
||||
NUMERIC : new Mode(0x1, [10, 12, 14]),
|
||||
ALPHANUMERIC: new Mode(0x2, [ 9, 11, 13]),
|
||||
BYTE : new Mode(0x4, [ 8, 16, 16]),
|
||||
KANJI : new Mode(0x8, [ 8, 10, 12]),
|
||||
ECI : new Mode(0x7, [ 0, 0, 0]),
|
||||
};
|
||||
|
||||
|
||||
// Private constructor.
|
||||
function Mode(mode, ccbits) {
|
||||
// (Package-private) The mode indicator bits, which is a uint4 value (range 0 to 15).
|
||||
Object.defineProperty(this, 'modeBits', {value:mode});
|
||||
|
||||
// (Package-private) Returns the bit width of the character count field for a segment in
|
||||
// this mode in a QR Code at the given version number. The result is in the range [0, 16].
|
||||
this.numCharCountBits = function(ver) {
|
||||
return ccbits[Math.floor((ver + 7) / 17)];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*---- Private helper functions and classes ----*/
|
||||
|
||||
// Returns a new array of bytes representing the given string encoded in UTF-8.
|
||||
function toUtf8ByteArray(str) {
|
||||
str = encodeURI(str);
|
||||
var result = [];
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
if (str.charAt(i) !== '%')
|
||||
result.push(str.charCodeAt(i));
|
||||
else {
|
||||
result.push(parseInt(str.substring(i + 1, i + 3), 16));
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* A private helper class that computes the Reed-Solomon error correction codewords for a sequence of
|
||||
* data codewords at a given degree. Objects are immutable, and the state only depends on the degree.
|
||||
* This class exists because each data block in a QR Code shares the same the divisor polynomial.
|
||||
* This constructor creates a Reed-Solomon ECC generator for the given degree. This could be implemented
|
||||
* as a lookup table over all possible parameter values, instead of as an algorithm.
|
||||
*/
|
||||
function ReedSolomonGenerator(degree) {
|
||||
if (degree < 1 || degree > 255)
|
||||
throw 'Degree out of range';
|
||||
|
||||
// Coefficients of the divisor polynomial, stored from highest to lowest power, excluding the leading term which
|
||||
// is always 1. For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}.
|
||||
var coefficients = [];
|
||||
|
||||
// Start with the monomial x^0
|
||||
for (var i = 0; i < degree - 1; i++)
|
||||
coefficients.push(0);
|
||||
coefficients.push(1);
|
||||
|
||||
// Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),
|
||||
// drop the highest term, and store the rest of the coefficients in order of descending powers.
|
||||
// Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D).
|
||||
var root = 1;
|
||||
for (var i = 0; i < degree; i++) {
|
||||
// Multiply the current product by (x - r^i)
|
||||
for (var j = 0; j < coefficients.length; j++) {
|
||||
coefficients[j] = ReedSolomonGenerator.multiply(coefficients[j], root);
|
||||
if (j + 1 < coefficients.length)
|
||||
coefficients[j] ^= coefficients[j + 1];
|
||||
}
|
||||
root = ReedSolomonGenerator.multiply(root, 0x02);
|
||||
}
|
||||
|
||||
// Computes and returns the Reed-Solomon error correction codewords for the given
|
||||
// sequence of data codewords. The returned object is always a new byte array.
|
||||
// This method does not alter this object's state (because it is immutable).
|
||||
this.getRemainder = function(data) {
|
||||
// Compute the remainder by performing polynomial division
|
||||
var result = coefficients.map(function() { return 0; });
|
||||
data.forEach(function(b) {
|
||||
var factor = b ^ result.shift();
|
||||
result.push(0);
|
||||
coefficients.forEach(function(coef, i) {
|
||||
result[i] ^= ReedSolomonGenerator.multiply(coef, factor);
|
||||
});
|
||||
});
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
// This static function returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and
|
||||
// result are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8.
|
||||
ReedSolomonGenerator.multiply = function(x, y) {
|
||||
if (x >>> 8 !== 0 || y >>> 8 !== 0)
|
||||
throw 'Byte out of range';
|
||||
// Russian peasant multiplication
|
||||
var z = 0;
|
||||
for (var i = 7; i >= 0; i--) {
|
||||
z = (z << 1) ^ ((z >>> 7) * 0x11D);
|
||||
z ^= ((y >>> i) & 1) * x;
|
||||
}
|
||||
if (z >>> 8 !== 0)
|
||||
throw 'Assertion error';
|
||||
return z;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* A private helper class that represents an appendable sequence of bits (0s and 1s).
|
||||
* Mainly used by QrSegment. This constructor creates an empty bit buffer (length 0).
|
||||
*/
|
||||
function BitBuffer() {
|
||||
Array.call(this);
|
||||
|
||||
// Appends the given number of low-order bits of the given value
|
||||
// to this buffer. Requires 0 <= len <= 31 and 0 <= val < 2^len.
|
||||
this.appendBits = function(val, len) {
|
||||
if (len < 0 || len > 31 || val >>> len !== 0)
|
||||
throw 'Value out of range';
|
||||
for (var i = len - 1; i >= 0; i--) // Append bit by bit
|
||||
this.push((val >>> i) & 1);
|
||||
};
|
||||
}
|
||||
|
||||
BitBuffer.prototype = Object.create(Array.prototype);
|
||||
BitBuffer.prototype.constructor = BitBuffer;
|
||||
|
||||
};
|
187
load/session.js
Normal file
187
load/session.js
Normal file
@ -0,0 +1,187 @@
|
||||
/**
|
||||
* This handles session specific aspects of each frame type, eg: sending to the baseline, clearing, accepting
|
||||
* input, moving around the frame (and windows within one if any)
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function Session() {
|
||||
'use strict';
|
||||
|
||||
// Our page object
|
||||
this.page = undefined;
|
||||
this.previous = undefined;
|
||||
|
||||
/* Frame type settings */
|
||||
this.settings = {};
|
||||
|
||||
/**
|
||||
* Enable pulling out submitted value by its name
|
||||
*
|
||||
* @param key
|
||||
* @returns {null|string|*}
|
||||
*/
|
||||
this.fieldValue = function(key) {
|
||||
for each (var k in this.page.input_fields) {
|
||||
if (k.name === key)
|
||||
return k.value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
this.get = function(page) {
|
||||
if (!(page instanceof PageObject))
|
||||
throw new Error('page must be a PageObject');
|
||||
|
||||
this.baselineSend('LOADING');
|
||||
// Just in case our new page doesnt exist
|
||||
this.previous = this.page;
|
||||
|
||||
this.page = new Page();
|
||||
var result = this.page.get(page);
|
||||
|
||||
// If our new page doesnt exist, reset our page back to the current
|
||||
if (! result)
|
||||
this.page = this.previous;
|
||||
|
||||
this.previous = undefined;
|
||||
this.baselineClear();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a message frame
|
||||
*
|
||||
* @param pagenum
|
||||
*/
|
||||
this.loadMessage = function(pagenum,ext) {
|
||||
log(LOG_ERROR,'Loading Message :['+pagenum+']');
|
||||
|
||||
// Load our message
|
||||
var ma = new MsgAreas()
|
||||
var area = ma.getArea(pagenum);
|
||||
var msg = ma.getMessage(pagenum);
|
||||
var msg_header;
|
||||
|
||||
if (! msg)
|
||||
return false;
|
||||
|
||||
// @todo Search 1zzzzEE..., 1zzzz...
|
||||
if (! this.get(new PageObject(MAIL_TEMPLATE_FRAME),ext)) {
|
||||
this.page = new Page();
|
||||
|
||||
log(LOG_ERROR,'Echomail template missing :['+JSON.stringify(MAIL_TEMPLATE_FRAME)+'] ?');
|
||||
|
||||
msg_header = 'TO: '+msg.to.substr(0,72)+"\r\n";
|
||||
msg_header += 'FROM: '+msg.from.substr(0,72)+"\r\n";
|
||||
msg_header += 'DATE: '+msg.date.substr(0,72)+"\r\n";
|
||||
msg_header += 'SUBJECT: '+msg.subject.substr(0,72)+"\r\n";
|
||||
|
||||
} else {
|
||||
// @todo change this to use atcode()
|
||||
msg_header = this.page.raw.replace(/@(.*)@/g,
|
||||
function (str, code, offset, s) {
|
||||
var length = code.split(':')[1];
|
||||
switch(code.split(':')[0]) {
|
||||
case 'DATE': return msg.date.substr(0,length);
|
||||
case 'FROM': return msg.from.substr(0,length);
|
||||
case 'SUBJECT': return msg.subject.substr(0,length);
|
||||
case 'TO': return msg.to.substr(0,length);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this.page.name = new PageObject(pagenum,'a');
|
||||
this.page.owner = 1;
|
||||
// @todo Keys should map to next/previous/send, etc as indicated in the template frame.
|
||||
this.page.key = [this.page.name.frame.substr(0,7)+'1',null,null,null,null,null,null,null,null,null];
|
||||
// @todo validate that FRAME_TYPE_MESSAGE is a message template
|
||||
this.page.type = FRAME_TYPE_MESSAGE;
|
||||
// @todo Take the cost from the template
|
||||
this.page.cost = 5;
|
||||
// @todo Take the key values from the template
|
||||
this.page.__properties__.isAccessible = true;
|
||||
this.page.__properties__.isPublic = true;
|
||||
|
||||
this.page.preload(msg_header+msg.content,'txt');
|
||||
|
||||
// Update the user's pointers
|
||||
var stats = ma.getUserStats(this.page.name.frame);
|
||||
|
||||
// if this message is to the user, and the msg number > scan_ptr and it is the next message on the user's new mail list
|
||||
var newmsgs = area.newMsgsToMe();
|
||||
var next;
|
||||
log(LOG_DEBUG,'User has: '+newmsgs.length-1+' msgs to read to ME');
|
||||
if (newmsgs.length) {
|
||||
next = newmsgs[1];
|
||||
log(LOG_DEBUG,'- NEXT is: '+next.tags+', this is: '+msg.tags);
|
||||
|
||||
if (next && (next.tags === msg.tags)) {
|
||||
log(LOG_DEBUG,'- Updating scan_ptr to: '+next.number);
|
||||
stats.scan_ptr = next.number;
|
||||
}
|
||||
|
||||
// Last message
|
||||
next = newmsgs[0];
|
||||
if (next !== undefined) {
|
||||
log(LOG_DEBUG,'- LAST TO ME is: '+next.tags);
|
||||
this.page.key[1] = area.page(next.tags);
|
||||
}
|
||||
|
||||
// Next new message
|
||||
next = newmsgs[2];
|
||||
if (next !== undefined) {
|
||||
log(LOG_DEBUG,'- NEXT TO ME is: '+next.tags);
|
||||
this.page.key[2] = area.page(next.tags);
|
||||
}
|
||||
}
|
||||
|
||||
// if this message is the next message, update last_read
|
||||
newmsgs = area.newMsgs();
|
||||
log(LOG_DEBUG,'User has: '+newmsgs.length+' msgs to read');
|
||||
if (newmsgs.length) {
|
||||
next = newmsgs[0];
|
||||
log(LOG_DEBUG,'- NEXT is: '+next.tags+', this is: '+msg.tags);
|
||||
|
||||
if (next.tags === msg.tags) {
|
||||
log(LOG_DEBUG,'- Updating last_read to: '+next.number);
|
||||
stats.last_read = next.number;
|
||||
}
|
||||
}
|
||||
|
||||
// Previous Message
|
||||
x = area.MessagePrev(this.page.name.frame);
|
||||
if (x)
|
||||
this.page.key[4] = area.page(x.tags);
|
||||
|
||||
// Next Message
|
||||
x = area.MessageNext(this.page.name.frame);
|
||||
if (x)
|
||||
this.page.key[6] = area.page(x.tags);
|
||||
|
||||
log(LOG_DEBUG,'Built frame: ['+this.page.name.frame+']['+this.page.name.index+'] ('+this.page.name.toString()+')');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Render the page
|
||||
this.render = function() {
|
||||
this.gotoxy(0,0);
|
||||
//console.clear(null,false);
|
||||
write(so.page.display().join(''));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a system message for a index
|
||||
*
|
||||
* @param index
|
||||
* @returns {string|*}
|
||||
* @see SessionProtocol()
|
||||
*/
|
||||
Session.prototype.getMessage = function(index) {
|
||||
eval('var msg = this.settings.'+index);
|
||||
return msg;
|
||||
}
|
596
load/session/ansitex.js
Normal file
596
load/session/ansitex.js
Normal file
@ -0,0 +1,596 @@
|
||||
const SESSION_ANSITEX = (1<<1);
|
||||
var 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
650
load/session/viewdata.js
Normal 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
1066
load/windows.js
Normal file
File diff suppressed because it is too large
Load Diff
20
logon.js
Normal file
20
logon.js
Normal file
@ -0,0 +1,20 @@
|
||||
require("sbbsdefs.js", 'SS_RLOGIN');
|
||||
//require("nodedefs.js", 'NODE_QUIET');
|
||||
|
||||
// To force all users to the ansitex shell
|
||||
// @todo Make a ini config setting for this
|
||||
user.command_shell = 'ansitex';
|
||||
|
||||
// Disable System Info and ?
|
||||
system.settings |= (SYS_NOSYSINFO | SYS_NONODELIST);
|
||||
|
||||
// @note: Unable to suppress "Logging on to <BBS> as <USER>..."
|
||||
|
||||
// Need to suppress Search for new messages & files
|
||||
user.settings &= ~(USER_ASK_SSCAN | USER_ASK_NSCAN | USER_ANFSCAN | USER_PAUSE | USER_NO_EXASCII | USER_COLDKEYS);
|
||||
|
||||
// Enable ANSI and some other settings
|
||||
user.settings |= (USER_AUTOTERM | USER_ANSI | USER_COLOR);
|
||||
|
||||
// Disable Chatting
|
||||
user.chat_settings |= (CHAT_NOPAGE | CHAT_NOACT);
|
8
text/frames_check.sh
Executable file
8
text/frames_check.sh
Executable 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
1
text/tex/198a
Normal file
@ -0,0 +1 @@
|
||||
{"version":1,"frame":198,"index":"a","owner":1,"cost":0,"content":"G1swbRtbNzZDG1sxbS4bWzBtDQogG1szM20bWG1zZ19ncnBfbmFtZTs0MBtcG1szMEMbWzM3bRtbMW3awr/Cv7+zG1swbQ0KIBtbMzZtG1htc2dfYXJlYV9hcmVhdGFnOzQwG1wbWzMwQxtbMzdtsyCzwrSzsw0KIBtbMTszNm0bWG1zZ19hcmVhX2Rlc2M7NDAbXBtbMzBDG1sxOzM3bVN1bW1hcnkbWzBtDQobWzE7MzBtxMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMSzxMTExMTExMTExMTExMTExMTExMTExMTExMTExBtbMG0NCiAbWERBVEVUSU1FOzI0G1wbWzI1QxtbMTszMG2zG1swbSAbWzFtTmV3IE1lc3NhZ2VzIHRvIFlvdRtbMG06IBtbMTszMW0bWG1zZ19hcmVhX25ld3RvbWU7LTUbXBtbMG0NChtbNTBDG1sxOzMwbbMbWzBtICAgICAbWzFtVW5yZWFkIE1lc3NhZ2VzG1swbTogG1sxOzMxbRtYbXNnX2FyZWFfbmV3Oy01G1wbWzBtDQogG1sxOzMybTEbWzM3bSBGaXJzdCB0byB5b3UgICAbWzM0bRtYbXNnX2FyZWFfbXNnb3RvbWVfZGF0ZTszMRtcG1swbSAbWzE7MzBtsxtbMG0gICAgICAbWzFtVG90YWwgTWVzc2FnZXMbWzBtOiAbWzE7MzFtG1htc2dfYXJlYV90b3RhbDstNRtcG1swbQ0KIBtbMTszMm0yG1swbSAbWzFtRmlyc3QgdW5yZWFkICAgG1sxOzM0bRtYbXNnX2FyZWFfbXNndW5yZWFkX2RhdGU7MzEbXBtbMG0gG1sxOzMwbbMbWzBtDQobWzUwQxtbMTszMG2zG1swbSAgICBQZW5kaW5nIE1lc3NhZ2VzOiAbWzMxbRtYbXNnX2FyZWFfcGVuZGluZzstNRtcG1szN20NCiAbWzE7MzJtMyAbWzM3bU9sZGVzdCBNZXNzYWdlIBtbMzRtG1htc2dfYXJlYV9tc2dvbGRlc3RfZGF0ZTszMRtcG1szN20gG1sxOzMwbbMbWzBtDQogG1sxOzMybTQgG1szN21MYXRlc3QgTWVzc2FnZSAbWzM0bRtYbXNnX2FyZWFfbXNnbmV3ZXN0X2RhdGU7MzEbXBtbMzdtIBtbMTszMG2zG1swbSAgICAgICAgICAbWzMybTkbWzBtIE5ldyBTY2FuOiAgIBtbMTszMm0bWG1zZ19hcmVhX25ld3NjYW47LTMbXBtbMG0NChtbNTBDG1sxOzMwbcEbWzBtDQogG1szMm01G1sxbSAbWzBtV3JpdGUgTmV3IE1lc3NhZ2UgICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzQ0bdXNzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3NzbgbWzBtDQogG1szMm02G1sxbSAbWzBtU2VhcmNoIGZvciBNZXNzYWdlICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzQ0bbMbWzMzbSCvIE1lc3NhZ2UgUmVhZGluZyBOYXZpZ2F0aW9uIK4gG1sxOzM3bbMbWzBtDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7NDRtsyAbWzMybRgbWzM2bSBTY3JvbGwgVVAgG1swOzQ0bSAgG1sxbbMbWzA7NDRtIBtbMTszMm0ZG1szNm0gU2Nyb2xsIERPV04bWzA7NDRtIBtbMTszM20gG1szN22zG1swbQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzQ0bbMgG1szMm0xG1szNm0gUHJldiBUbyBNZSAgG1sxOzMzbbMbWzA7NDRtIBtbMTszMm0yG1szNm0gTmV4dCBUbyBNZSAgG1szM20gG1szN22zG1swbQ0KIBtbMzJtOBtbMzdtIEFyZWEgU3VtbWFyeSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTs0NG2zG1szM20gG1szMm00IBtbMzZtUHJldmlvdXMgICAgG1sxOzMzbbMbWzA7NDRtIBtbMzJtMxtbMTszNm0gG1swOzM2OzQ0bVByZXYgVGhyZWFkG1szN20gG1sxOzMzbSAbWzM3bbMbWzBtDQogG1sxOzMybTAbWzA7MzJtIBtbMTszN21BcmVhIERlc2NyaXB0aW9uICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7NDRtsxtbMzNtIBtbMzJtNhtbMzZtIE5leHQbWzFtIBtbMDs0NG0gICAgICAgG1sxbbMbWzA7NDRtIBtbMzJtNxtbMzdtIBtbMzZtTmV4dCBUaHJlYWQbWzM3bSAbWzE7MzNtIBtbMzdtsxtbMG0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTs0NG2zG1swOzQ0bSAbWzMybTUbWzM3bSAbWzM2bVdyaXRlIE5ldxtbMzdtICAgG1sxbbMbWzA7NDRtIBtbMzJtOBtbMzdtIBtbMzZtUmVwbHkbWzM3bSAgICAgICAbWzE7MzNtIBtbMzdtsxtbMG0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTs0NG2zG1swOzQ0bSAbWzMybSMbWzM3bSAbWzM2bU1zZyBBdHRycyAgG1szN20gG1sxbbMbWzA7NDRtIBtbMTszMm0wG1szN20gG1sxOzM2bVJldHVybiBoZXJlG1szN20gG1sxOzMzbSAbWzM3bbMbWzBtDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7NDRt1M3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3NvhtbMG0NCg==","isPublic":0,"isAccessible":1,"type":"m","key":[null,null,null,null,null,null,null,null,null,null],"date":"2022-04-29T00:00:00.000Z"}
|
1
text/tex/199a
Normal file
1
text/tex/199a
Normal file
@ -0,0 +1 @@
|
||||
{"version":1,"frame":199,"index":"a","owner":1,"cost":0,"content":"DQogG1sxOzM2bURhdGU6IBtbMzdtQERBVEU6NjBADQogICAbWzE7MzZtVG86IBtbMzdtQFRPOjYwQA0KIBtbMTszNm1Gcm9tOiAbWzM3bUBGUk9NOjYwQA0KIBtbMTszNm1TdWJqOiAbWzM3bUBTVUJKRUNUOjYwQA0KIBtbMTszMG3ExMTExMQbWzBtxMTExMTEG1sxOzMwbcTExMTExBtbMG3ExMTExMQbWzE7MzBtxMTExMTEG1swbcTExMTExBtbMTszMG3ExMTExMQbWzBtxMTExMTEG1sxOzMwbcTExMTExBtbMG3ExMTExMQbWzE7MzBtxMTExMTEG1swbQ0K","isPublic":0,"isAccessible":1,"type":"m","key":[null,null,null,null,null,null,null,null,null,null],"date":"2022-04-29T00:00:00.000Z"}
|
1
text/tex/1a
Normal file
1
text/tex/1a
Normal 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
1
text/tex/980a
Normal 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
1
text/tex/981a
Normal 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
1
text/tex/981b
Normal 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
1
text/tex/983a
Normal 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
1
text/tex/98a
Normal 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
1
text/tex/98b
Normal 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
1
text/tex/998a
Normal 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
1
text/tex/999a
Normal file
File diff suppressed because one or more lines are too long
1
text/tex/999b
Normal file
1
text/tex/999b
Normal file
File diff suppressed because one or more lines are too long
1
text/tex/99a
Normal file
1
text/tex/99a
Normal 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
1
text/tex/99b
Normal 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
1
text/vtx/1a
Normal 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
1
text/vtx/980a
Normal 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
1
text/vtx/981a
Normal 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
1
text/vtx/981b
Normal 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
1
text/vtx/983a
Normal 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
1
text/vtx/98a
Normal 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
1
text/vtx/98b
Normal 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
1
text/vtx/998a
Normal 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
1
text/vtx/999a
Normal 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
1
text/vtx/999b
Normal 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
1
text/vtx/99a
Normal 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
1
text/vtx/99b
Normal 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
243
tools/export.js
Normal file
@ -0,0 +1,243 @@
|
||||
load('smbdefs.js');
|
||||
|
||||
// ANSItex specific includes
|
||||
load('ansitex/load/defs.js');
|
||||
load('ansitex/load/funcs.js');
|
||||
|
||||
/**
|
||||
* Work out which key should be used for a page, or validate if a key used is valid
|
||||
*
|
||||
* @param page
|
||||
* @param signed
|
||||
* @returns {*}
|
||||
*/
|
||||
function getKey(page,signed) {
|
||||
log(LOG_DEBUG,'+ CHECKING key:'+signed+' for page: '+page);
|
||||
|
||||
var f = new File(file_cfgname(system.mods_dir,'ansitex/ctrl/videotex.ini'));
|
||||
var match = '';
|
||||
|
||||
if (f.open("r")) {
|
||||
var BreakException = {};
|
||||
|
||||
// Default Key
|
||||
var key = f.iniGetValue('prefix','key');
|
||||
|
||||
try {
|
||||
f.iniGetSections("prefix:").forEach(function (prefix) {
|
||||
var p = prefix.substr(7);
|
||||
var pk = f.iniGetValue(prefix, 'Key', '');
|
||||
var re = new RegExp('^' + p, 'g');
|
||||
|
||||
// If it was signed, is the key a value one
|
||||
if (signed && signed.length) {
|
||||
if (pk === signed) {
|
||||
//print('SIGNED key is valid:' + signed);
|
||||
key = signed;
|
||||
throw BreakException;
|
||||
}
|
||||
|
||||
// Which key should sign this page
|
||||
} else if (page.toString().match(re) && (p.length > match.length)) {
|
||||
match = p;
|
||||
key = pk;
|
||||
}
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
if (e !== BreakException) throw e;
|
||||
}
|
||||
}
|
||||
|
||||
f.close();
|
||||
|
||||
log(LOG_DEBUG,'- key:'+key);
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export message from message base
|
||||
*
|
||||
* @param msgbase
|
||||
*/
|
||||
function msgBaseExport(msgbase) {
|
||||
var ini = new File(msgbase.file+'.ini');
|
||||
var i=0;
|
||||
|
||||
if (ini.open("r")) {
|
||||
export_ptr=ini.iniGetValue('videotex','export_ptr',0);
|
||||
ini.close();
|
||||
|
||||
} else {
|
||||
log(LOG_ERROR,'! ERROR: Unable to open INI for ['+msgbase.file+']');
|
||||
}
|
||||
|
||||
// If pointer doesnt exist, reset it from the message base.
|
||||
if (export_ptr === undefined) {
|
||||
var f = new File(file_getcase(msgbase.file+'.sbl'));
|
||||
|
||||
if (f.open('rb')) {
|
||||
export_ptr = f.readBin(4);
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
|
||||
highest = export_ptr;
|
||||
var total_msgs = msgbase.total_msgs;
|
||||
|
||||
log(LOG_DEBUG,'| msgBaseExport: export_ptr='+export_ptr+'|last_msg='+msgbase.last_msg+'|total_msgs='+total_msgs);
|
||||
|
||||
if (msgbase.last_msg >= export_ptr)
|
||||
i = total_msgs-(msgbase.last_msg-export_ptr);
|
||||
|
||||
for (; i<total_msgs; i++) {
|
||||
if (js.terminated)
|
||||
break;
|
||||
|
||||
var idx = msgbase.get_msg_index(true,i);
|
||||
if (! idx) {
|
||||
log(LOG_ERROR,'! ERROR: Reading index of msg offset ['+i+'] : ['+msgbase.error+']');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip message until we get to our last one read.
|
||||
if (idx.number <= export_ptr)
|
||||
continue;
|
||||
|
||||
if (idx.number > highest)
|
||||
highest = idx.number;
|
||||
|
||||
// Only check messages from Videotex
|
||||
if (idx.from !== crc16_calc('videotex')) {
|
||||
log(LOG_DEBUG,'! IGNORING: Message offset ['+i+'] in ['+msgbase.file+']');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the message header
|
||||
var hdr = msgbase.get_msg_header(true,i);
|
||||
|
||||
// Ignore deleted messages
|
||||
if (hdr.attr&MSG_DELETE) {
|
||||
log(LOG_DEBUG,'! IGNORING: Deleted message offset ['+i+'] in ['+msgbase.file+']');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore locally posted messages
|
||||
if (! hdr.from_net_type) {
|
||||
log(LOG_DEBUG,'! IGNORING: Local message offset ['+i+'] in ['+msgbase.file+']');
|
||||
continue;
|
||||
}
|
||||
|
||||
log(LOG_DEBUG,'+ PROCESSING: Message offset ['+i+'] in ['+msgbase.file+'] from ['+hdr.from_net_addr+'] ('+hdr.subject+')');
|
||||
|
||||
var body = msgbase.get_msg_body(
|
||||
/* by_offset: */true,
|
||||
i,
|
||||
/* strip Ctrl-A */true,
|
||||
/* rfc822-encoded: */false,
|
||||
/* include tails: */false);
|
||||
|
||||
t = new File(system.temp_dir+'videotex.gpg');
|
||||
if (! t.open('w+')) {
|
||||
log(LOG_ERROR,'! ERROR: Unable to open temp file ['+system.temp_dir+'videotex.gpg'+']');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
t.write(base64_decode(body));
|
||||
t.close();
|
||||
|
||||
} catch (error) {
|
||||
log(LOG_ERROR,error);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Check if the signature is good
|
||||
result = system.exec('gpgv --keyring '+system.mods_dir+'ansitex/keys/pubring.kbx '+system.temp_dir+'videotex.gpg');
|
||||
|
||||
if (result !== 0 ) {
|
||||
log(LOG_ERROR,'! ERROR: Invalid Signature for message offset ['+i+'] in ['+msgbase.file+']');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (file_exists(system.temp_dir+'videotex.tex'))
|
||||
file_remove(system.temp_dir+'videotex.tex')
|
||||
|
||||
// Check that the signature is allowed to author the frames
|
||||
result = system.exec('gpg --homedir '+system.mods_dir+'ansitex/keys --batch --status-fd 2 -o '+system.temp_dir+'videotex.tex '+system.temp_dir+'videotex.gpg 2>'+system.temp_dir+'videotex.log');
|
||||
|
||||
if (result !== 0 ) {
|
||||
log(LOG_ERROR,'! ERROR: Failed to extract message offset ['+i+'] in ['+msgbase.file+']');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Put frame in place.
|
||||
f = new File(system.temp_dir+'videotex.tex');
|
||||
if (! f.exists || ! f.open('r')) {
|
||||
log(LOG_ERROR,'! ERROR: Failed to open frame for message offset ['+i+'] in ['+msgbase.file+']');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
frame = JSON.parse(f.read());
|
||||
x = new Frame(0);
|
||||
frame.render = x.render;
|
||||
|
||||
// @todo Figure out how to delete this duplicate code
|
||||
Object.defineProperty(frame,'page', {
|
||||
get: function() {return this.frame+this.index}
|
||||
});
|
||||
|
||||
x = null;
|
||||
|
||||
} catch (error) {
|
||||
log(LOG_ERROR,error);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the key that signed the message
|
||||
f = new File(system.temp_dir+'videotex.log');
|
||||
if (! f.exists || ! f.open('r')) {
|
||||
log(LOG_ERROR,'! ERROR: Failed to open gpg log for message offset ['+i+'] in ['+msgbase.file+']');
|
||||
continue;
|
||||
}
|
||||
|
||||
var signed = '';
|
||||
while (string = f.readln()) {
|
||||
var matches = string.match(/\s+GOODSIG\s+.*\<(.*)\>/);
|
||||
|
||||
if (matches) {
|
||||
signed = matches[1];
|
||||
}
|
||||
}
|
||||
f.close();
|
||||
|
||||
if (signed !== getKey(frame.frame,signed)) {
|
||||
log(LOG_ERROR,'! ERROR: Key ['+signed+' is not authorised for message offset ['+i+'] in ['+msgbase.file+']');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save the frame.
|
||||
log(LOG_INFO,'Updating page ['+frame.page+'] from ['+hdr.from_net_addr+'] signed with ['+signed+']');
|
||||
saveFrame(frame);
|
||||
}
|
||||
|
||||
if (ini.open(file_exists(ini.name) ? 'r+':'w+')) {
|
||||
ini.iniSetValue('videotex','export_ptr',highest);
|
||||
ini.close();
|
||||
|
||||
} else {
|
||||
log(LOG_ERROR,'! ERROR: Unable to save to INI for ['+msgbase.file+']');
|
||||
}
|
||||
}
|
||||
|
||||
var msgbase = new MsgBase(findMsgBase(null).code);
|
||||
log(LOG_DEBUG,'+ ANSITEX_EXPORT: Open message base in ['+msgbase.file+']');
|
||||
|
||||
if (! msgbase.open()) {
|
||||
log(LOG_ERROR,'! ERROR: Unable to open ['+msgbase.file+']');
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
msgBaseExport(msgbase);
|
||||
msgbase.close();
|
42
tools/frame_load.js
Normal file
42
tools/frame_load.js
Normal 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
144
tools/frames_check.js
Normal 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
19
tools/frames_list.js
Normal 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
20
tools/frames_tag.js
Normal 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
4
tools/msgbases_list.js
Normal file
@ -0,0 +1,4 @@
|
||||
load('ansitex/load/msgbases.js');
|
||||
|
||||
var ma = new MsgAreas();
|
||||
ma.list;
|
119
tools/save.js
Normal file
119
tools/save.js
Normal file
@ -0,0 +1,119 @@
|
||||
// ANSItex specific includes
|
||||
load('ansitex/load/defs.js');
|
||||
load('ansitex/load/funcs.js');
|
||||
|
||||
var options = loadOptions();
|
||||
|
||||
// Modes of operation:
|
||||
// -s -p x -i y = To send a stored page via the msgbase
|
||||
// {-s} -p x -i y -f file = To update (and optionally send) a page with new content
|
||||
// -all details = to create a new page
|
||||
|
||||
// Import
|
||||
send = (argv.indexOf('-s') >= 0);
|
||||
// Page
|
||||
p = getArg('-p','No page specified with -p',true);
|
||||
// Index
|
||||
i = getArg('-i','No index specified with -i',true);
|
||||
// File to convert
|
||||
file = getArg('-f','No file specified with -f',false);
|
||||
|
||||
frame = new TexFrame();
|
||||
frame.load(pageStr({frame: p,index: i}));
|
||||
|
||||
if (! send || ! frame || file) {
|
||||
if (frame.page === null) {
|
||||
frame = new TexFrame();
|
||||
frame.frame = p;
|
||||
frame.index = i;
|
||||
}
|
||||
|
||||
// Key
|
||||
key = getArg('-k','No index specified with -k',false);
|
||||
// Cost
|
||||
cost = getArg('-c','No index specified with -c',false);
|
||||
// Owner
|
||||
owner = getArg('-o','No owner specified with -o',false);
|
||||
// Owner
|
||||
type = getArg('-t','No type specified with -t',false);
|
||||
|
||||
if (file) {
|
||||
f = new File(file);
|
||||
if (! f.exists || ! f.open('r')) {
|
||||
log(LOG_ERROR,'! ERROR: Unable to open ['+file+']');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
frame.content = base64_encode(f.read());
|
||||
f.close();
|
||||
}
|
||||
|
||||
if (owner) {
|
||||
frame.owner = base64_encode(owner.replace(/\\1/g,"\1"));
|
||||
}
|
||||
|
||||
if (key) {
|
||||
frame.key = key.split(',').map(function(t){return parseInt(t)});
|
||||
|
||||
if (frame.key.length !== 10) {
|
||||
log(LOG_ERROR,'! ERROR: Must specify 10 keys with -k');
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Public
|
||||
if (argv.indexOf('-P') >= 0)
|
||||
frame.isPublic = true;
|
||||
|
||||
if (argv.indexOf('-A') >= 0)
|
||||
frame.isAccessible = true;
|
||||
|
||||
if (cost)
|
||||
frame.cost = cost;
|
||||
|
||||
if (type)
|
||||
frame.type = type; // @todo validate this is a valid type.
|
||||
|
||||
// Date
|
||||
frame.date = new Date().toISOString();
|
||||
|
||||
// Final validation
|
||||
if (! frame.owner) {
|
||||
log(LOG_ERROR,'! ERROR: No owner specified ['+file+']');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Store the frame in file
|
||||
frame.save();
|
||||
}
|
||||
|
||||
// @NOTE: We need to use a binary signature then base64 encode it, as mailers may strip 0x0a while messages are in transit.
|
||||
if (send === 1 && options.gpg_key) {
|
||||
if (! file) {
|
||||
file = system.mods_dir+'ansitex/text/'+frame.page+'.tex';
|
||||
}
|
||||
|
||||
if (! file_exists(file)) {
|
||||
log(LOG_ERROR,'! ERROR: File '+file+' doesnt exist?');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (file_exists(file+'.asc'))
|
||||
file_remove(file+'.asc')
|
||||
|
||||
result = system.exec('gpg --homedir '+system.mods_dir+'ansitex/keys --clearsign --batch --local-user '+options.gpg_key+' -s '+file);
|
||||
w = new File(file+'.asc');
|
||||
|
||||
if (w.open('r')) {
|
||||
msg = base64_encode(w.read());
|
||||
|
||||
} else {
|
||||
log(LOG_ERROR,'! ERROR: Unable to send with GPG for '+frame.page + ' Error: '+w.error);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
w.close();
|
||||
|
||||
msgBaseImport(null,frame.page,msg);
|
||||
printf('GPG Result: %s',result);
|
||||
}
|
Loading…
Reference in New Issue
Block a user