packages/core/src/index.ts
/**
* Iniquity Core
* @module Core
* @summary This is the Iniquity core bbs library. It's the foundation of any Iniquity application.
* @example
* ```typescript
* import { IQ } from "@iniquitybbs/core"
*
* const iq = new IQ()
*
* iq.artwork({ filename: "./path/to/textfile.ans" }).render({ speed: 10 })
*
* iq.say("Pretty cool, right???".newlines(2).color("bright cyan").center()).pause()
*
* iq.disconnect()
* ```
*/
// import { IQCoreAssets } from "./assets/index"
/*
-$a. ------------------ .a$ ---------------------------- %$!, ----------------%
`$¸ .%$$^¸$$aa. .¸$` . .a$a$$. `¸$% $a$. .
-.aaa$ $$$$'- $$$$$.- $aaa. -.a%$^"$$aa -- .$$$$$'- $$a. $aaa. `$,$ ----------%
;$$$$',a$a$ d$%$$$$$,'$$$$;$$$$$ $$$$$., $$%$$" d$a$$ '$$$$; $$$ .a%$ $$a
:$$$$;$$$$%; Z$$$$$$$$;$$$$:$$$$$. $$$$^$,;$$&$$ Z$$$$,;$$$$.a$$$a..$$$ $$$
.$$$$ `$$$$. $$$%$$$' $$$$.`$$$$ $$%$$$$ `$$$$. $%$$$ $$$$""$$$" $$$$: a$$
`$$$a.$$%$ $$$$$$';a$$$` `¸$$aa$$$$&$': `$$$$a. $$$$'a$$$`.$$'$ $$$$; $$$
%-----.------ $$$$'--------------- $$%$$' -- `¸$$$$$%$¸' ---- $$¸$a. `"$&$$//$%$
dz . .:'¸' . . $$$$' . . `¸$$$$y. `$$&
%--------- a`-----------.--------- $$¸' -----.------------.---------------- $$$
. !a . . . . .:' . . . .:.a$$$¸
. . '$a, . a` . 'a . . s` . . .
. ¸$Aa . !a a! . . .. %s .s
. ¸¸' . . '$$Aa.aA$$' . . `!$%a.a%//$
==============================================================================
t h e i n i q u i t y b u l l e t i n b o a r d s y s t e m
==============================================================================
*/
load("sbbsdefs.js")
load("frame.js")
load("layout.js")
load("lightbar.js")
load("progress-bar.js")
load("scrollbar.js")
load("tree.js")
load("funclib.js")
load("event-timer.js")
load("event-emitter.js")
load("termsetup.js")
load("cterm_lib.js")
load("qengine.js")
load("utf8_cp437.js")
load("utf8_ascii.js")
load("utf8_utf16.js")
load("ansiterm_lib.js")
load("ansiedit.js")
load("loadfonts.js")
load("graphic.js")
console.inactivity_warning = 9999
console.inactivity_hangup = 99999
if (!Object.entries) {
Object.entries = function (obj: any) {
let ownProps = Object.keys(obj),
i = ownProps.length,
resArray = new Array(i)
while (i--) resArray[i] = [ownProps[i], obj[ownProps[i]]]
return resArray
}
}
// TODO get core.js in here and use all those pollifills instead of making my own.
if (!String.prototype.includes) {
String.prototype.includes = function (search, start) {
// @ts-expect-error
if (search instanceof RegExp) throw TypeError("first argument must not be a RegExp")
if (start === undefined) start = 0
return this.indexOf(search, start) !== -1
}
}
export interface IQReactorOptions {
model: any
observe: Function
notify: Function
}
/**
* Iniquity reactive data model
* @see https://www.monterail.com/blog/2016/how-to-build-a-reactive-engine-in-javascript-part-1-observable-objects
* @param dataObj
* @returns
*/
export function IQReactor(dataObj: any): IQReactorOptions {
const signals = {}
observeData(dataObj)
// Besides the reactive data object, we also want to return and thus expose the observe and notify functions.
return {
model: dataObj,
observe,
notify
}
/**
*
* @param obj
* @param key
*/
function makeReactive(obj: any, key: any) {
let val = obj[key]
Object.defineProperty(obj, key, {
get() {
return val // Simply return the cached value
},
set(newVal) {
val = newVal // Save the newVal
notify(key) // Ignore for now
}
})
}
// Iterate through our object keys
/**
*
* @param obj
*/
function observeData(obj: any) {
for (const key in obj) if (obj.hasOwnProperty(key)) makeReactive(obj, key)
}
/**
*
* @param property
* @param signalHandler
*/
function observe(property: any, signalHandler: any) {
// alert(property)
// @ts-expect-error
if (!signals[property]) signals[property] = [] // If there is NO signal for the given property, we create it and set it to a new array to store the signalHandlers
// @ts-expect-error
signals[property].push(signalHandler) // We push the signalHandler into the signal array, which effectively gives us an array of callback functions
}
/**
*
* @param signal
* @param newVal
*/
function notify(signal: any, newVal?: any) {
// @ts-expect-error
if (!signals[signal] || signals[signal].length < 1) return // Early return if there are no signal handlers
// @ts-expect-error
signals[signal].forEach((signalHandler) => signalHandler()) // We call each signalHandler that’s observing the given property
}
}
/**
* Artwork render options
*/
export interface IQArtworkRenderOptions {
/**
* The full path to your art folder
*/
basepath?: string | undefined
/**
* The file name of the artwork you would like to render to the screen
*/
filename?: string | undefined
/**
* How fast you would like the artwork rendered to the screen.
*/
speed?: number | undefined
/**
* Supported character encodings are CP437 and UTF8.
*/
encoding?: "CP437" | "UTF8"
/**
* The rendering mode to use. e.g. Line at a time, character at a time, etc.
*/
mode?: "line" | "character" | "@-codes" | "reactive"
/**
* A boolean representing if you want the screen cleared before.
*/
clearScreenBefore?: boolean
data?: unknown
}
/**
* Iniquity bbs string operations
* @interface
* @summary Some pretty typical operations most sysops end up wanting apply to strings. Got more ideas? Let me know.
* @param {boolean} colorReset Make sure that colors don't bleed on to the terminal after displaying the text.
* @param {number} newlines How many newlines to display after the text has been displayed.
* @param {boolean} center Will attempt to center the text on the clients terminal.
*/
export interface IQStringUtils {
colorReset?: boolean | false
newlines?: number | 0
center?: boolean | false
}
export type IQPauseOptions = IQStringUtils
export interface IQPrintOptions extends IQStringUtils {
text: string
}
interface IBBSSayOptions {
text: string
}
export interface IQArtworkOptions {
basepath?: string
filename?: string
}
/**
* User options
* @param name The name of the user.
* @param password The users password.
*/
export interface IUserOptions {
name: string
password: string
}
/**
* Additional functions exported by render
* @function pause What pause does.
* @function colorReset Resets the current lines screen color back to normal.
*/
export interface IQArtworkRenderFunctions {
/**
* @param {IQPauseOptions} options
* @see {@link IQPauseOptions}
*/
pause(options?: IQPauseOptions): void
/**
* Resets the screen color
*/
colorReset(): void
gotoxy(x: number, y: number): void
prompt(x: number, y: number, text?: string): void
cursor(x: number, y: number): void
}
export interface IQMenuPromptOptions {}
export interface IQMenuPromptFunctions {
/**
* Go to the specified coordinates on the terminal.
* This function can also be chained with .command()
* @param x Width coordinates.
* @param y Height coordinates.
*/
gotoxy(x: number, y: number): void | any
/**
* Execute a menu command, with optional callback for working with response data.
* This function can also be chained with .gotoxy()
* @param command
* @param callback
*/
command(command: Function, callback?: Function): void
}
export interface IBBSSayFunctions {
/**
* @param {IQPauseOptions} options
* {@link IQPauseOptions}
*/
pause(options?: IQPauseOptions): void
wait(options?: IQWaitOptions | number): void
gotoxy(x: number, y: number): void
}
export interface IBBSPrintFunctions {
/**
* @param {IQPauseOptions} options
* @see {@link IQPauseOptions}
*/
pause(options?: IQPauseOptions): void
wait(options?: IQWaitOptions | number): void
gotoxy(x: number, y: number): void
}
/**
* Menu options
* @param {IQMenuOptions} options Some options.
* @see {@link IQMenuOptions}
*/
export interface IQMenuOptions {
name?: string
description?: string
prompt?: IQMenuPromptOptions
cmdkeys?: string
commands: {
[s: string]: (...args: any) => any
}
frame?: IQFrameOptions
data?: any
}
export interface IQMenuLoopMessageResponse {
[x: string]: any
}
export interface IQFrameOptions {
x: number
y: number
width: number
height: number
color: IQFrameColorOptions
artwork?: string
parent?: any
transparent?: boolean
scrollbars?: boolean
}
export interface IMenuCommands {
command: IMenuCommand
}
export interface IMenuCommand {
key: number
name: string
}
export interface IQMenuPromptOptions {
x?: number
y?: number
text?: string
}
/**
* Iniquity Core BBS
* @summary The main class you will use as it wraps all the other classes in a unified API.
* @example
* ```typescript
* import BBS from "@iniquitybbs/core/dist/iniquity"
*
* const mybbs = new BBS()
*
* myIniquity.say("Say hi!").pause()
*
* myIniquity.hangup()
*
* ```
*/
export abstract class IQBaseConfig {
public basepath!: string
public assets!: string
public access!: IQModuleACLS
public encoding!: "CP437" | "UTF8"
public data!: IQReactorOptions
public computed!: any
}
export class Iniquity extends IQBaseConfig {
/**
* Terminal information available to iniquity
*/
public terminfo: IQTermInfoObject = {
x: console.screen_columns,
y: console.screen_rows,
terminal: console.terminal,
type: console.type,
charset: console.charset
}
/**
* Says something to the user. Does not parse MCI/@- codes.
* @param {IBBSSayOptions | string} options What you would like to say on the screen.
* @see {@link IQPrintOptions} to learn more about the available options.
* @returns {IBBSSayFunctions}
* @example
* ```typescript
* iq.say("Say something to the terminal!")
* iq.say("This time say something but do some cool string manipulation.".newlines(2).color("bright red").center()).pause()
* ```
*/
public say(options: IBBSSayOptions | string | object | any): IBBSSayFunctions {
switch (typeof options) {
case "string":
console.writeln(options)
break
case "object":
console.writeln(JSON.stringify(options))
break
default:
console.writeln(options)
break
}
return {
pause(options?: IQPauseOptions): void {
pause(options)
},
wait(options?: IQWaitOptions): void {
wait(options)
},
gotoxy(x: number, y: number): void {
gotoxy(x, y)
}
}
}
/**
* Prints something to the user. Parses Renegade MCI/Synchronet @- codes.
* @param {IQPrintOptions | string} options you would like to print on the screen.
* @see {@link IQPrintOptions} to learn more about the available options.
* @returns {IBBSPrintFunctions}
* @example
* ```typescript
* iq.print("Display some text on the screen that can parse @ codes.").center()
* iq.print("Display some text on the screen that can parse @ codes.".newlines(2).color("background red"))
* iq.print("Display some text on the screen that can parse @ codes.".color("cyan")).pause()
* ```
*/
public print(options: IQPrintOptions | string): IBBSPrintFunctions {
typeof options === "string" ? console.putmsg(options) : console.putmsg(options.text ?? new Error("Sometthing is wrong with this string."))
return {
pause(options?: IQPauseOptions): void {
pause(options)
},
wait(options?: IQWaitOptions): void {
wait(options)
},
gotoxy(x: number, y: number): void {
gotoxy(x, y)
}
}
}
/**
* Display a pause prompt on the screen.
* @summary This pause prompt does the usual stuff. It also provides a few helpers via its return functions.
* @param options
* @param {IQPauseOptions}
* @see {@link IQPauseOptions} to learn more about the available options.
*/
public pause(options?: IQPauseOptions): void {
if (options?.colorReset) console.putmsg("".color("reset"))
console.putmsg("".newlines(options?.newlines ?? 0))
console.pause()
}
/**
* Sends the cursor to a particular coordinates on the screen
* @param x
* @param y
*/
public gotoxy(x: number, y: number): void {
console.gotoxy(x, y)
}
/**
* Sends the cursor to a particular coordinates on the screen
* @param {IQCursorOptions} options
*/
public cursor(options?: IQCursorOptions): IQCursorChainableMethods {
const actions: IQCursorChainableMethods = {
errors: [],
up(rows = 1): IQCursorChainableMethods {
console.up(rows)
return this
},
down(rows = 1): IQCursorChainableMethods {
console.down(rows)
return this
},
left(cols = 1): IQCursorChainableMethods {
console.left(cols)
return this
},
right(cols = 1): IQCursorChainableMethods {
console.right(cols)
return this
}
}
return actions
}
/**
* Halt the screen for a specified period of time.
* @param {IQWaitOptions} options In miliseconds
* @returns void
* @example
* iq.wait(100)
* wait(10)
* this.wait(1000)
*/
public wait(options?: IQWaitOptions | number): void {
if (typeof options === "number") sleep(options)
else if (options?.milliseconds !== undefined) sleep(options.milliseconds)
else sleep(100)
}
/**
* Displays a prompt (value) and returns a string of user input (ala clent-side JS)
* @param question
* @param callback
* @returns response
*/
public ask(question: string, callback?: any): string | undefined {
if (!callback) return prompt(question)
callback(prompt(question))
}
/**
* Will disconnect the user immediately.
* @returns void
*/
public disconnect(): void {
bbs.hangup()
}
public logoff(): void {
bbs.logoff()
}
public logout(): void {
bbs.logout()
}
/**
* User stuff
* @summary It doesn't do much right now. But it does create new users and store them in the SBBS backend.
* @param {IUserOptions} options An object containing the various configuration properties.
* @see {@link IUserOptions} to learn more about the available options.
* @returns {User} An instance of User and its return functions.
*/
public user(options: IUserOptions): User {
return new User(options)
}
/**
* Will allow you to
* render artwork to the screen
* @param {IQArtworkOptions} options An object containing the various configuration properties.
* @see {@link IQArtworkOptions} to learn more about the available options.
* @returns {Artwork} An instance of Artwork and its return functions.
* @example
* ```typescript
* const artwork = iq.artwork({ basepath: "/iniquity/core/src/assets/" })
* artwork.render({ filename: Assets.we_iniq3 })
*
* iq.artwork({ basepath: "/iniquity/core/src/assets/", filename: Assets.we_iniq3 }).render({ clearScreenBefore: false })
* ```
*/
public artwork(options?: IQArtworkOptions): Artwork {
return new Artwork({ basepath: options?.basepath ?? this.basepath ?? undefined, filename: options?.filename ?? undefined })
}
/**
* Menu instance
* @param {IQMenuOptions} options An object containing the various configuration properties.
* @returns {IQMenu} An instance of Menu
*/
public menu(options: IQMenuOptions): IQMenu {
return new IQMenu(options)
}
/**
* Frame instance
* @param {IQFrameOptions} options An object containing the various configuration properties.
* @returns {IQFrame} An instance of Menu
*/
public frame(options: IQFrameOptions): IQFrame {
return new IQFrame(options)
}
}
export abstract class IQ extends Iniquity {
public abstract start(): any
}
export interface IQCursorOptions {
[s: string]: any
}
export interface IQCursorChainableMethods {
errors: []
up: (rows?: number) => IQCursorChainableMethods
down: (rows?: number) => IQCursorChainableMethods
left: (cols?: number) => IQCursorChainableMethods
right: (cols?: number) => IQCursorChainableMethods
}
export interface IQWaitOptions {
milliseconds?: number
}
export interface IQAskOptions {
text?: string
}
export interface IQTermInfoObject {
x: number
y: number
terminal: any
type: any
charset: any
}
export interface IQMenuLoopOptions {
/**
* Basically halt everything for the specified period of milliseconds.
* @param milliseconds
*/
wait?: number
/**
* The maximum number of the menu event loop that can run.
*/
maxInterval?: number
/**
* Data you would like to be reactive in the menu
*/
data?: any
}
export class IQMenu {
public name?: string
public description?: string
protected cmdkeys: string | null = null
public commands!: IQMenuOptions["commands"]
constructor(options: IQMenuOptions) {
if (options.name) this.name = options.name
if (options.description) this.description = options.name
if (options.commands) this.commands = options.commands
for (const [cmdkey, command] of Object.entries(this.commands!)) if (cmdkey != "default") this.cmdkeys += cmdkey
}
public render(module: Function, options?: IQMenuLoopOptions): void {
let count = 0
let cache: IQMenuLoopMessageResponse = {}
const maxInterval = options?.maxInterval ?? 10000000
do {
count++
bbs.nodesync()
bbs.user_sync()
const res: IQMenuLoopMessageResponse = {
options: options,
interval: count,
system: system,
// @ts-expect-error
cmdkey: console.inkey(K_UPPER).toString() ?? null
}
if (res.cmdkey === cache.cmdkey) continue
else this.commands[res.cmdkey] ? module(res, this.commands[res.cmdkey]()) : module(res, null)
cache = res
iq.wait(options?.wait)
if (maxInterval! >= count) continue
else break
} while (bbs.online && !js.terminated)
}
/**
* Will render a prompt to the screen.
* @param {IQMenuPromptOptions} options
* @returns {IQMenuPromptFunctions} Functions that can be chained to the prompt.
* @example
* menu.prompt({ x: 20, y: 30, text: "Feed me: " }).command(cmdkey)
* menu.prompt({ text: "Enter your command: ".color("bright cyan"), x: 20, y: 20 }).command(cmdkey)
* menu.prompt({ x: 20, y: 30, text: "Feed me: " }).command(cmdkey, (response, error) => {
* if (response.error) {
* alert(errors)
* }
* })
*/
public prompt(options?: IQMenuPromptOptions | string): IQMenuPromptFunctions {
if (!this.commands) throw new Error("No commands appear to be configured for this menu.")
const commands = Object.keys(this.commands)
.filter((cmdkey) => cmdkey != "default")
.join(",")
// @ts-expect-error
if (typeof options === "string") console.putmsg(commands + ":" + options, P_NONE, 4)
if (typeof options === "object") {
if (options.x && options.y) console.gotoxy(options.x, options.y)
// @ts-expect-error
if (options.text !== undefined) console.putmsg(commands + ":" + options.text, P_NONE, 4)
}
return {
gotoxy(x: number, y: number) {
iq.gotoxy(x, y)
return {
command(command: Function, callback?: Function) {
this.command(command, callback)
}
}
},
command(command: Function, callback?: Function) {
if (typeof command !== "function") return
if (typeof callback !== "function") return
if (callback) callback(command)
else command()
}
}
}
public keypressed(cmdkeys: string | null = this.cmdkeys): string {
// @ts-expect-error
return console.getkeys(cmdkeys ?? this.cmdkeys, K_UPPER)
// var char = console.inkey(K_UPPER)
// console.putmsg(char)
}
}
export class IQFrame implements IQFrameOptions {
private frame: any | null = null
public is_open!: boolean
public transparent!: boolean
public scrollbars!: boolean
public x!: number
public y!: number
public width!: number
public height!: number
public color!: IQFrameColorOptions
/**
* toggle true/false to restrict/allow frame movement outside display
*
*/
public checkbounds!: boolean
constructor(options: IQFrameOptions) {
// @ts-ignore
this.frame = new Frame(options.x, options.y, options.width, options.height, options.color, options.parent)
if (options.artwork) this.frame.load(options.artwork)
}
public open(): void {
this.frame!.open()
}
public draw(): void {
this.frame!.draw()
}
public delete(): void {
this.frame!.delete()
}
public cycle(): void {
this.frame!.cycle()
}
public refresh(): void {
this.frame!.refresh()
}
public close(): void {
this.frame!.close()
}
public print(message: string): void {
this.frame!.putmsg(message)
}
public say(message: string): void {
this.frame!.putmsg(message)
}
public gotoxy(x: number, y: number): void {
this.frame!.gotoxy(x, y)
}
public loop(runtime: Function, interval?: number): void {
// @ts-ignore
if (!this.is_open) this.open()
do {
runtime()
this.cycle()
sleep(interval ?? 100)
} while (bbs.online && !js.terminated)
}
public load(filename: any) {
this.frame.load(filename)
}
}
export enum IQFrameColorOptions {
// @ts-ignore
black = BG_BLACK,
// @ts-ignore
blue = BG_BLUE
}
export enum IQFrameColorOptions {
// @ts-ignore
black = BG_BLACK,
// @ts-ignore
blue = BG_BLUE
}
export class Group {}
/**
* Network
* @summary I can't wait to get started on this!
*/
export class Network {}
/**
* User
* @summary Some basic user utils. More to follow.
*/
export class User {
public name = ""
public password = ""
/**
* Mechanisms for working with an individual iniquity user
* @param options.name
* @param options.password
* @param options
*/
constructor(options: IUserOptions) {
this.name = options.name
this.password = options.password
}
login() {
const user = bbs.login(this.name, null, this.password)
if (user) return user
else return undefined
}
exists(): boolean {
const user = bbs.login(this.name)
if (user) return true
else return false
}
new() {
// TODO check for all return properties of this sbbs system.new_user
const user = system.new_user(this.name) as { password: string }
if (user) {
say(JSON.stringify(user)).pause()
user.password = this.password
}
return user
}
}
/**
* Text
* @summary Core text file display and manipulation capabilities
*/
export class Text {}
/**
* Iniquity Core Artwork
* @summary Core artwork display and manipulation capabilities
* @example
* ```typescript
* import { Artwork } from "@iniquitybbs/core"
*
* const art = new Artwork({ filename: "./path/to/file.ans"})
*
* art.render({ speed: 50}).pause()
*
* art.render({ mode: "line", clearScreenBefore: true }).colorReset().pause()
*
* ```
*/
export class Artwork extends Iniquity {
public filename: string | undefined
private fileHandle: any
/**
* The Iniquity Artwork rendering class
* @param {IQArtworkOptions} options An object containing the various configuration properties.
* @see {@link IQArtworkOptions}
* @returns {Artwork} An instance of Artwork
*/
constructor(options: IQArtworkOptions) {
super()
this.basepath = options.basepath ?? this.basepath
this.filename = options.filename ?? undefined
}
/**
* Render
* @summary Display ANSI/ASCII/PETSCII text files onto the screen
* @param {IQArtworkRenderOptions} options An object containing the various configuration parameters.
* @see {@link IQArtworkRenderOptions}
* @returns {IQArtworkRenderFunctions} Will render the artwork on the screen as well as provide various render functions.
* @example
* ```typescript
* import { Artwork } from "@iniquitybbs/core"
*
* const art = new Artwork()
* art.render({ mode: "line", speed: 100 }).clearScreen().pause()
* ```
*/
render(options?: IQArtworkRenderOptions): IQArtworkRenderFunctions {
if (options?.clearScreenBefore === true) console.putmsg("@POFF@@CLS@@PON@".color("reset"))
const basepath = options?.basepath ?? this.basepath
const filename = options?.filename ?? this.filename ?? new Error("I need to know what file to display!")
const encoding = options?.encoding ?? this.encoding
const mode = options?.mode ?? "@-codes"
const speed = options?.speed ?? 30
const data = options?.data ?? { message: "test" }
switch (mode) {
case "reactive":
// @ts-ignore asd
console.printfile(`${this.basepath}/${filename}`, P_WORDWRAP, 4, data)
break
case "@-codes":
// @ts-ignore these damn constants.
console.putmsg(`@TYPE:${this.basepath}/${filename}@`, P_WORDWRAP, 4, data)
break
case "line":
console.line_counter = 0
// @ts-ignore Using Synchronet's JS File operations
this.fileHandle = new File(`${this.basepath}/${filename}`)
if (!this.fileHandle.open("r")) alert("Iniquity: Error opening file: " + `${this.basepath}/${filename}`)
const text = this.fileHandle.readAll()
for (let i = 0; i < text.length; i++) {
// // @ts-ignore these damn constants.
// if (encoding === "CP437") console.putmsg(text[i], null, null, data)
// // @ts-ignore these damn constants.
// if (encoding === "UTF8") console.putmsg(utf8_cp437(text[i]), null, null, data)
if (text[i].includes("SAUCE")) continue
// @ts-ignore
else console.putmsg(text[i], P_NONE, 4, data)
wait(speed)
if (i < text.length - 1) console.putmsg("\r\n")
console.line_counter = 0
}
this.fileHandle.close()
break
case "character":
// @ts-expect-error
const graphic = new Graphic(iq.terminfo.x, iq.terminfo.y)
graphic.cpm_eof = false
if (!graphic.load(`${this.basepath}/${filename}`)) {
alert("Load failure")
} else {
const normalized = graphic.normalize()
normalized.draw(undefined, undefined, undefined, undefined, undefined, undefined, options?.speed ?? undefined)
}
break
default:
// nothing...
}
return {
pause(options?: IQPauseOptions): void {
if (options) this.pause({ colorReset: options.colorReset ?? false, center: options.center ?? false })
else console.putmsg("".color("reset"))
console.pause()
},
colorReset(): void {
console.putmsg("".color("reset"))
},
cursor(x: number, y: number): void {
console.gotoxy(x, y)
},
gotoxy(x: number, y: number): void {
console.gotoxy(x, y)
},
prompt(x: number, y: number, text?: string): void {
console.gotoxy(x, y)
if (text) console.putmsg(text)
}
}
}
}
declare global {
export interface String {
/**
* Sets the color of the text in the string.
* @param color Choose from any capable ANSI escape sequence which defines a color.
* @returns The newly formatted string.
*/
color(
color:
| "black"
| "red"
| "green"
| "yellow"
| "blue"
| "magenta"
| "cyan"
| "white"
| "bright black"
| "bright red"
| "bright green"
| "bright yellow"
| "bright blue"
| "bright magenta"
| "bright cyan"
| "bright white"
| "background black"
| "background red"
| "background green"
| "background yellow"
| "background blue"
| "background magenta"
| "background cyan"
| "background white"
| "background bright black"
| "background bright red"
| "background bright green"
| "background bright yellow"
| "background bright blue"
| "background bright magenta"
| "background bright cyan"
| "background bright white"
| "reset"
): string
gotoxy(x: number, y: number): string
/**
* Prepends a newline to the beginning of the text
* @param count The total number of newlines. Default to 1.
* @returns The newly formatted string
*/
newlines(count?: number): string
/**
* Will center the text between the available columns on the screen
* @returns The newly formatted string
*/
center(): string
}
}
String.prototype.color = function (color: string): string {
switch (color) {
// 16 colors...
case "black":
return "\u001b[30m" + this
case "bright black":
return "\u001b[30;1m" + this
case "red":
return "\u001b[31m" + this
case "bright red":
return "\u001b[31;1m" + this
case "green":
return "\u001b[32m" + this
case "bright green":
return "\u001b[32;1m" + this
case "yellow":
return "\u001b[33m" + this
case "bright yellow":
return "\u001b[33;1m" + this
case "blue":
return "\u001b[34m" + this
case "bright blue":
return "\u001b[34;1m" + this
case "magenta":
return "\u001b[35m" + this
case "bright magenta":
return "\u001b[35;1m" + this
case "cyan":
return "\u001b[36m" + this
case "bright cyan":
return "\u001b[36;1m" + this
case "white":
return "\u001b[37m" + this
case "bright white":
return "\u001b[37;1m" + this
// background colors...
case "background black":
return "\u001b[40m" + this
case "background red":
return "\u001b[41m" + this
case "background green":
return "\u001b[42m" + this
case "background yellow":
return "\u001b[43m" + this
case "background blue":
return "\u001b[44m" + this
case "background magenta":
return "\u001b[45m" + this
case "background cyan":
return "\u001b[46m" + this
case "background white":
return "\u001b[46m" + this
// background bright colors
case "background bright black":
return "\u001b[40;1m" + this
case "background bright red":
return "\u001b[41;1m" + this
case "background bright green":
return "\u001b[42;1m" + this
case "background bright yellow":
return "\u001b[43;1m" + this
case "background bright blue":
return "\u001b[44;1m" + this
case "background bright magenta":
return "\u001b[45;1m" + this
case "background bright cyan":
return "\u001b[46;1m" + this
case "background bright white":
return "\u001b[47;1m" + this
case "reset":
case "clear":
return "\u001b[0m" + this
default:
return "\u001b[0m" + this
}
}
String.prototype.gotoxy = function (x: number, y: number): string {
console.gotoxy(x, y)
return this as string
}
String.prototype.center = function (): string {
console.center(this as string)
return this as string
}
String.prototype.newlines = function (count?: number | 0): string {
let string = ""
for (let i = 0; i <= count!; i++) string += "\r\n"
return string + this
}
/**
* Say whatever you want, but to the screen
* @param {IBBSSayOptions} options
* @returns {IBBSSayFunctions}
* @example
* say("are we making it here?").pause()
*/
export function say(options?: IBBSSayOptions | string | object | any): IBBSSayFunctions {
iq.say(options)
return {
pause(options?: IQPauseOptions): void {
pause(options)
},
wait(options?: IQWaitOptions): void {
wait(options)
},
gotoxy(x: number, y: number): void {
gotoxy(x, y)
}
}
}
/**
* A pretty cool method for working with cursor movement.
* @param {IQCursorOptions} options
* @returns {IQCursorChainableMethods}
* @example
* cursor().down(10).up(12).down().up().down().left(1).right(20).down(12).up(14)
*/
export function cursor(options?: IQCursorOptions): IQCursorChainableMethods {
return iq.cursor(options)
}
/**
*
* @param x
* @param y
*/
export function gotoxy(x: number, y: number): void {
iq.gotoxy(x, y)
}
/**
*
* @param options
*/
export function pause(options?: IQPauseOptions): void {
iq.pause()
}
/**
*
* @param options
*/
export function wait(options?: IQWaitOptions | number): void {
iq.wait(options)
}
/**
*
* @param question
* @param callback
*/
export function ask(question: string, callback?: any): string | undefined {
if (!callback) return iq.ask(question)
else callback(iq.ask(question))
}
/**
*
* @param options
*/
export function artwork(options: IQArtworkOptions): Artwork {
return iq.artwork(options)
}
/**
*
* @param options
*/
export function menu(options: IQMenuOptions): IQMenu {
return iq.menu(options)
}
export namespace IQ {
/**
*
* @param x
* @param y
*/
export function gotoxy(x: number, y: number): void {
iq.gotoxy(x, y)
}
/**
*
* @param options
*/
export function say(options: IBBSSayOptions | string): void {
iq.say(options)
}
/**
*
* @param options
*/
export function pause(options?: IQPauseOptions): void {
iq.pause(options)
}
/**
*
* @param options
*/
export function wait(options?: IQWaitOptions): void {
iq.wait(options)
}
/**
*
* @param question
* @param callback
*/
export function ask(question: string, callback?: any): string | undefined {
if (!callback) return iq.ask(question)
else callback(iq.ask(question))
}
/**
*
* @param options
*/
export function artwork(options: IQArtworkOptions): Artwork {
return iq.artwork(options)
}
/**
*
* @param options
*/
export function menu(options: IQMenuOptions): IQMenu {
return iq.menu(options)
}
export class Core extends Iniquity {}
export namespace Core {
// export class Module imp IQModule {}
export class Menu extends IQMenu {}
export class Frame extends IQFrame {}
// export function Module(options: IQModuleOptions): (constructor: Function) => void {
// return IQModule(options)
// }
/**
*
* @param options
*/
export function Reactor(options: IQReactorOptions): any {
return IQReactor(options)
}
}
// NOTE this all sucks, need to find a better home for this.
export namespace Decorators {
/**
*
* @param options
*/
export function Module(options: IQModuleOptions): (constructor: Function) => void {
return IQModule(options)
}
/**
*
* @param options
*/
export function ModuleRuntime(options: IQModuleRuntimeOptions): any {
return IQModuleRuntime(options)
}
}
export enum IQFrameColorOptions {}
}
declare let console: ISSBSConsole
interface ISBBSServer {
[x: string]: any
}
interface ISBBSUser {
[x: string]: any
}
interface ISBBSClient {
[x: string]: any
}
interface ISBBSGlobal {
[x: string]: any
}
/**
* Issbsconsole
*/
declare interface ISSBSConsole {
screen_rows: number
type: any
terminal: any
charset: any
screen_columns: number
up(arg0: number): void
down(arg0: number): void
left(arg0: number): void
right(arg0: number): void
writeln: any
center(arg0: string): void
gotoxy(x: number, y: number): void
log: any
print: any
inactivity_warning: number
inactivity_hangup: number
putmsg: any
line_counter: number
clear: any
pause: any
getkeys(commands: string, constant?: string): void
inkey: any
}
/**
* Isbbssystem
*/
interface ISBBSSystem {
new_user(name: string): object
newuser_password: string
put_node_message(index: number, arg1: string): boolean
nodes: number
get_node(node: number): ISBBSSystemNodeListProperties
name: string
operator: string
/**
* The node list.
*/
node_list: ISBBSSystemNodeListProperties[]
/**
* The bbs stats
*/
stats: any
}
interface ISBBSSystemNodeListProperties {
status: number
errors: number
action: number
useron: number
connection: number
misc: number
aux: number
extaux: number
dir: string
}
interface ISBBSBbs {
[x: string]: any
}
// import * as path from "path"
// import yargs from "yargs"
// /**
// * Os
// * @class
// * @implements yargs.CommandModule
// */
// class Core implements yargs.CommandModule {
// public command = "core [command]"
// public describe = "make a get HTTP request"
// public builder = (yargs: yargs.Argv) => {
// return yargs.commandDir(path.join(__dirname + "/commands"), { recurse: true, exclude: RegExp("/*.spec.*/") }).pkgConf("iniquity")
// }
// public handler(argv: yargs.Arguments) {}
// }
// const cmds: yargs.CommandModule = new Core()
// if (process.argv.length > 2) yargs.command(cmds).help().argv
/**
* The globally scoped intance of iniquity
*/
const iq = new Iniquity()
export default iq
/** Decorators */
export enum IQModuleACLS {
low = 1,
medium = 2,
high = 3,
superHigh = 4
}
export type IQModuleOptions = IQBaseConfig
/**
* An experimental Iniquity module decorator for bbs modules
* @author ispyhumanfly
* @param {IQModuleOptions} options
*/
/**
*
* @param options
*/
export function IQModule(options: IQModuleOptions) {
return function (constructor: Function) {
constructor.prototype.basepath = options.basepath
constructor.prototype.assets = options.assets
constructor.prototype.access = options.access
constructor.prototype.data = options.data
constructor.prototype.computed = options.computed
// if (options?.access === IQModuleACLS.low) {
// iq.say("You do can't access this module.".color("red")).pause()
// // iq.disconnect()
// }
}
}
/**
* The IQ script executed as part of a module.
* @param {IQModuleRuntimeOptions} options
* @returns
*/
export function IQModuleRuntime(options?: IQModuleRuntimeOptions): any {
return function (target: any, propertyKey: string, descriptor?: PropertyDescriptor): any {
if (options?.clearScreenBefore === true) iq.print("@POFF@@CLS@@PON@".color("reset"))
}
}
export interface IQModuleRuntimeOptions {
debug?: boolean
clearScreenBefore?: boolean
}
export { Assets as IQCoreAssets } from "./assets/index"
declare global {
export namespace js {
export function setImmediate(callback: (...args: any) => void, ...args: any): void
export function setTimeout(handler: TimerHandler, timeout?: number | undefined, ...arguments: any[]): number
export function setInterval(handler: TimerHandler, timeout?: number | undefined, ...arguments: any[]): object
export function addEventListener(event: string, handler: Function): number
export function removeEventListener(id: number): number
export function dispatchEvent(event: string, thisObj?: object): void
export let terminated: boolean
export let do_callbacks: boolean
export let scope: object
export let global: object
export let version: string
}
/**
* add a line of text to the server and/or system log, values are typically string constants or variables, level is the debug level/priority (default: LOG_INFO)
* @param level
* @param value
*/
export function log(level: any, value: string): string
/**
* stop script execution, optionally setting the global property exit_code to the specified numeric value
* @param exit_code
*/
export function exit(exit_code: number): void
/**
* produce a tone on the local speaker at specified frequency for specified duration (in milliseconds)
* @param frequency
* @param duration
*/
export function beep(frequency: number, duration: number): void
export function load(library: string): void
export function alert(text: string): void
export function prompt(text: string): string
export function sleep(miliseconds: number): void
export function yield(forced: boolean): number
export function random(max_number: number): number
export function time(): number
export let client: ISBBSClient
export let system: ISBBSSystem
export let server: ISBBSServer
export let user: ISBBSUser
export let bbs: ISBBSBbs
}
/**
* Utf8s cp437
* @param arg0
* @returns cp437
*/
declare function utf8_cp437(string: any): string
/**
* Utf8s ascii
* @param string
* @returns ascii
*/
declare function utf8_ascii(string: any): string
/**
* Utf8s utf16
* @param string
* @returns utf16
*/
declare function utf8_utf16(string: any): string
// export let K_UPPER: any
/**
* Return a single asset random selected from an array of IQCoreAssets
* @param {IQCoreAssets[]} assets An array of IQCoreAssets to choose from
* @returns {IQCoreAssets} The randomly selected asset
*/
export function randomAsset(assets: string[]): string {
return assets[Math.floor(Math.random() * assets.length)]
}