Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added counting and help #36

Merged
merged 1 commit into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ The library is designed to be compatible with both module systems, so you can ch

- `encode(hex: string): string` — Convert hex transaction into UTF-16BE.
- `decode(data: string): string` — Convert UTF-16BE into hex transaction.
- `count(data: string, type: 'sms' | 'mms'): number` — Count the number of characters/SMS/MMS needed for the transaction.
- `getEndpoint(network?: number | string, countriesList?: string | Array<string>): { [key: string]: Array<string> }` — Get an object of SMS endpoints (phone numbers) per country.
- `sms(number?: boolean | string | number | Array<string>, message?: string, network?: number | string, encodeMessage?: boolean, platform?: string): string` — Create an SMS URI based on the provided parameters.
- `mms(number?: boolean | string | number | Array<string>, message?: string, network?: number | string, encodeMessage?: boolean, platform?: string): string` — Create an MMS URI based on the provided parameters.
Expand Down Expand Up @@ -181,10 +182,12 @@ Types:
- `--version` (`-v`) - Get the version of the library.
- `--encode` (`-e`) - Encode the HEX transaction.
- `--decode` (`-d`) - Decode the UTF-16BE transaction.
- `--count` (`-ct`) - Count the number of characters needed for the transaction. You can choose type of count: `sms`, `mms`. (To perform a count, you need to provide `encode` command.)
- `--getendpoint` (`-g`) - Get the SMS/MMS endpoint for the network and country.
- `--sms` - Create an SMS URI based on the provided parameters.
- `--mms` - Create an MMS URI based on the provided parameters.
- `--download` (`-dl`) - Boolean value to download a file with the encoded content as `.txms.txt` file in your working directory.
- `--download` (`-dl`) - Boolean value to download a file with the encoded content as `.txms.txt` file in your working directory. (To download a file, you need to provide `encode` command.)
- `--help` (`-h`) - Show help. (Only for TTY mode.)

### Piping

Expand Down
86 changes: 75 additions & 11 deletions bin/txms
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ function parseArgs(argv) {
countryCodes: null, // For getEndpoint
phoneNumbers: null, // For SMS/MMS phone numbers
download: false, // Flag to indicate download
countType: null, // Count type: sms, mms, or null for characters
showHelp: false, // Flag to indicate if help is requested
};

argv.forEach((arg) => {
Expand All @@ -35,6 +37,9 @@ function parseArgs(argv) {
args.kind = 'decode';
args.value = value;
break;
case '--count':
args.countType = value || true;
break;
case '--getendpoint':
args.kind = 'getendpoint';
args.value = value; // Network type for getEndpoint
Expand All @@ -59,6 +64,9 @@ function parseArgs(argv) {
case '--countries':
args.countryCodes = value.split(','); // Comma-separated country codes
break;
case '--help':
args.showHelp = true;
break;
default:
break;
}
Expand All @@ -76,6 +84,9 @@ function parseArgs(argv) {
args.kind = 'decode';
args.value = value;
break;
case '-ct':
args.countType = value || true;
break;
case '-g':
args.kind = 'getendpoint';
args.value = value;
Expand All @@ -97,6 +108,9 @@ function parseArgs(argv) {
case '-c':
args.countryCodes = value.split(',');
break;
case '-h':
args.showHelp = true;
break;
default:
break;
}
Expand All @@ -109,12 +123,46 @@ function parseArgs(argv) {
return args;
}

// Function to display help
function displayHelp(newline = '\n') {
const helpText = `
\x1b[1mUsage:\x1b[0m txms \x1b[38;5;214m[options]\x1b[0m

\x1b[1mOptions:\x1b[0m
\x1b[38;5;214m-v, --version\x1b[0m Get the version of the library.
\x1b[38;5;214m-e, --encode\x1b[0m Encode the HEX transaction.
\x1b[38;5;214m-d, --decode\x1b[0m Decode the UTF-16BE transaction.
\x1b[38;5;214m-ct, --count\x1b[0m Count the number of characters needed for the transaction. You can choose the type of count: 'sms' or 'mms'.
\x1b[38;5;214m-g, --getendpoint\x1b[0m Get the SMS/MMS endpoint for the network and country.
\x1b[38;5;214m-s, --sms\x1b[0m Create an SMS URI based on the provided parameters.
\x1b[38;5;214m-m, --mms\x1b[0m Create an MMS URI based on the provided parameters.
\x1b[38;5;214m-dl, --download\x1b[0m Download a file with the encoded content as .txms.txt file in your working directory.
\x1b[38;5;214m-o, --output\x1b[0m Specify the output directory for downloads.
\x1b[38;5;214m-f, --filename\x1b[0m Specify the filename for downloads.
\x1b[38;5;214m-c, --countries\x1b[0m Specify a comma-separated list of country codes.
\x1b[38;5;214m-h, --help\x1b[0m Show this help message and exit.

\x1b[1mExamples:\x1b[0m
txms --encode=yourHexValue
txms -e=yourHexValue --download
txms -d=yourUTF16String
echo yourHexValue | txms --encode
`;
process.stdout.write(helpText + newline);
}

// Parse the arguments
const args = parseArgs(process.argv.slice(2));

// If the help flag is set, display the help message immediately
if (args.showHelp && process.stdin.isTTY) {
displayHelp();
process.exit(0);
}

if (process.stdin.isTTY) {
// If the script is run with a TTY, process the command-line arguments
run(args.kind, args.value, args.output, args.countryCodes, args.phoneNumbers, args.download, args.filename, '\n');
run(args.kind, args.value, args.output, args.countryCodes, args.phoneNumbers, args.download, args.filename, args.countType, '\n');
} else {
// If data is being piped into the script, capture it
let content = '';
Expand All @@ -124,16 +172,18 @@ if (process.stdin.isTTY) {
});
process.stdin.on('end', () => {
content = content.trim();

if (!content) {
run(args.kind, args.value, args.output, args.countryCodes, args.phoneNumbers, args.download, args.filename, '');
if (!content && !args.showHelp) {
run(args.kind, args.value, args.output, args.countryCodes, args.phoneNumbers, args.download, args.filename, args.countType, '');
} else if (args.showHelp) {
displayHelp();
process.exit(0);
} else {
run(args.kind, content, args.output, args.countryCodes, args.phoneNumbers, args.download, args.filename, '');
run(args.kind, content, args.output, args.countryCodes, args.phoneNumbers, args.download, args.filename, args.countType, '');
}
});
}

async function run(kind, value, output, countryCodes, phoneNumbers, download, filename, newline) {
async function run(kind, value, output, countryCodes, phoneNumbers, download, filename, countType, newline) {
if (!value && kind !== 'version') {
process.stderr.write('Value is required' + newline);
process.exit(1);
Expand All @@ -152,12 +202,26 @@ async function run(kind, value, output, countryCodes, phoneNumbers, download, fi
process.exit(0);
} else if (kind === 'encode' || kind === 'e') {
if (download) {
const filenm = await txms.downloadMessage(value, filename ? filename : undefined, output);
process.stdout.write(`TxMS file was downloaded as "${filenm}".${newline}`);
} else {
const encoded = txms.encode(value);
process.stdout.write(encoded + newline);
const filenamePrint = await txms.downloadMessage(value, filename ? filename : undefined, output);
process.stdout.write(`TxMS file was downloaded as "${filenamePrint}".${newline}`);
process.exit(0);
}

if (countType) {
let calculated;
if (countType === 'sms') {
calculated = txms.count(value, 'sms');
} else if (countType === 'mms') {
calculated = txms.count(value, 'mms');
} else {
calculated = txms.count(value);
}
process.stdout.write(calculated + newline);
process.exit(0);
}

let encodedMessage = txms.encode(value);
process.stdout.write(encodedMessage + newline);
process.exit(0);
} else if (kind === 'decode' || kind === 'd') {
const decoded = txms.decode(value);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "txms.js",
"version": "1.2.9",
"version": "1.2.10",
"description": "Transaction messaging service protocol",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
42 changes: 42 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export interface Transport {
encode(hex: string): string;
decode(data: string): string;
count(hex: string, type?: 'sms' | 'mms' | true): number;
getEndpoint(network?: number | string, countriesList?: string | Array<string>): { [key: string]: Array<string> };
sms(number?: boolean | string | number | Array<string>, message?: string, network?: number | string, encodeMessage?: boolean, platform?: string): string;
mms(number?: boolean | string | number | Array<string>, message?: string, network?: number | string, encodeMessage?: boolean, platform?: string): string;
Expand Down Expand Up @@ -85,6 +86,47 @@ const txms: Transport = {
return '0x' + hex.replace(/^0+/, '');
},

count(hex: string, type?: 'sms' | 'mms' | true): number {
// Encode the hex string to the message
const message = this.encode(hex);

// If type is not provided, return the length of the message in characters
if (!type) {
return message.length;
}

// SMS calculation logic
if (type === 'sms') {
// UTF-16 encoding, so the character limit is 70 characters for a single SMS
const singleSmsLimit = 70;
const multipartSmsLimit = 67; // Character limit per SMS in a concatenated message

if (message.length <= singleSmsLimit) {
// Fits within a single SMS
return 1;
} else {
// Calculate the number of segments required for a multipart SMS
return Math.ceil(message.length / multipartSmsLimit);
}
}

// MMS calculation logic
if (type === 'mms') {
// Assume a typical size limit of 300 KB for MMS (this may vary by carrier)
const mmsSizeLimit = 300 * 1024; // 300 KB in bytes
// Estimate size of the message in bytes (UTF-16, so each character is 2 bytes)
const messageSizeInBytes = message.length * 2;

if (messageSizeInBytes <= mmsSizeLimit) {
return 1;
} else {
return Math.ceil(messageSizeInBytes / mmsSizeLimit);
}
}

return message.length;
},

getEndpoint(network?: number | string, countriesList?: string | Array<string>): { [key: string]: Array<string> } {
let requestedList: Array<string> | undefined;
if (countriesList instanceof Array) {
Expand Down
64 changes: 61 additions & 3 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ if (!fs.existsSync(outputDir)) {
// Encode/Decode Tests
describe('Encode/Decode Tests', () => {
samples.valid.forEach((f) => {
test(`OK - Encode - to data. Description: ${f.description}`, () => {
test(`Should encode - to data. Description: ${f.description}`, () => {
const actual = txms.encode(f.hex);
assert.strictEqual(actual, f.data);
});
});

samples.valid.forEach((f) => {
test(`OK - Decode - to hex. Description: ${f.description}`, () => {
test(`Should decode - to hex. Description: ${f.description}`, () => {
const actual = txms.decode(f.data);
const normalizedActual = actual.startsWith('0x') ? actual.slice(2) : actual;
const normalizedExpected = f.hex.startsWith('0x') ? f.hex.slice(2) : f.hex;
Expand All @@ -37,12 +37,33 @@ describe('Encode/Decode Tests', () => {
});

samples.invalid.forEach((f) => {
test(`Encode — ${f.description}`, () => {
test(`Should encode — ${f.description}`, () => {
assert.throws(() => {
txms.encode(f.hex);
}, /Not a hex format/);
});
});

samples.valid.forEach((f) => {
test(`Should count - characters. Description: ${f.description}`, () => {
const length = txms.count(f.hex);
assert.strictEqual(length, f.length);
});
});

samples.valid.forEach((f) => {
test(`Should count - SMS. Description: ${f.description}`, () => {
const lengthSms = txms.count(f.hex, 'sms');
assert.strictEqual(lengthSms, f.sms);
});
});

samples.valid.forEach((f) => {
test(`Should count - MMS. Description: ${f.description}`, () => {
const lengthSms = txms.count(f.hex, 'mms');
assert.strictEqual(lengthSms, f.mms);
});
});
});

// Endpoint Tests
Expand Down Expand Up @@ -263,4 +284,41 @@ describe('CLI Tests', () => {
assert.strictEqual(result.status, 0);
assert.strictEqual(result.stdout.toString().trim(), samples.valid[0].hex);
});

test('Should count length', () => {
const hexValue = samples.valid[0].hex;
const result = spawnSync('node', [txmsPath, `--encode=${hexValue}`, '--count']);
assert.strictEqual(result.status, 0);
assert.strictEqual(parseInt(result.stdout.toString(), 10), samples.valid[0].length);
});

test('Should count amount of SMS', () => {
const hexValue = samples.valid[0].hex;
const result = spawnSync('node', [txmsPath, `--encode=${hexValue}`, '--count=sms']);
assert.strictEqual(result.status, 0);
assert.strictEqual(parseInt(result.stdout.toString(), 10), samples.valid[0].sms);
});

test('Should count amount of MMS', () => {
const hexValue = samples.valid[0].hex;
const result = spawnSync('node', [txmsPath, `--encode=${hexValue}`, '-ct=mms']);
assert.strictEqual(result.status, 0);
assert.strictEqual(parseInt(result.stdout.toString(), 10), samples.valid[0].mms);
});

test('Should count with piping', () => {
const hexValue = samples.valid[0].hex;
const echo = spawnSync('echo', [hexValue]);
const result = spawnSync('node', [txmsPath, '--encode', '--count'], {
input: echo.stdout
});
assert.strictEqual(result.status, 0);
assert.strictEqual(parseInt(result.stdout.toString(), 10), samples.valid[0].length);
});

test('Should print help text', () => {
const result = spawnSync('node', [txmsPath, '--help']);
assert.strictEqual(result.status, 0);
assert.match(result.stdout.toString(), /^\n\x1B\[1mUsage:\x1B\[0m txms \x1B\[38;5;214m\[options\]\x1B\[0m/);
});
});
10 changes: 8 additions & 2 deletions test/samples.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@
{
"hex": "0xf8d880843b9aca008252080396ab2896c9af969b11394e3f0be750da75d0076e1d1afd880de0b6b3a764000080b8ab16acb60152c254244a6e3edcd5e66c029caf909f4f2dc4972ff01716d96de84afc62d94c47810b55b38eb0c3572b728552611d3fbb0209e100fde2e8404d168dd431c6d3dd2eec1574d86ad34e2374506cb98ee4576bbf5a78f07003c653d9e8887582020fb369c16fe1ae5e46f0dd4e0b00771fb7ab745c15389be291530859010281f2746b652118a47b07b9763c789e90576f6a2585d530a89892aa3aba2577895e63a3492cbbf38d00",
"data": "~Ǹǘ肄㮚쨀艒ࠃ隫⢖즯際ᄹ丿௧僚痐ݮᴚﶈ~čǠ뚳Ꝥ~ĀĀ肸ꬖ겶Œ쉔⑊渾~ǜǕ~ǦŬʜ꾐齏ⷄ霯~ǰėᛙ淨䫼拙䱇脋喳躰썗⭲蕒愝㾻ȉ~ǡĀ~ǽǢ~Ǩŀ䴖跔㇆폝⻬ᕴ~ǘŪ퍎⍴偬릎~Ǥŗ殿婸~ǰŰφ叙~Ǩƈ疂ȏ덩셯~ǡƮ幆~ǰǝ下wᾷꭴ尕㢛~ǢƑ匈夁ʁ~DzŴ步℘ꑻ~ćƹ瘼碞遗潪▅픰ꢘ銪㪺╷襞掣䤬믳贀",
"description": "Correct transaction on Devin."
"description": "Correct transaction on Devin.",
"length": 145,
"sms": 3,
"mms": 1
},
{
"hex": "f8dc821ae4850ee6b280008252080196cb65d677385703c528527f2a0f0e401b4af1988d91c5896e3f4f2ab21845000080b8abcffa127f34f8dc8d8bc9a50da5def786a16ecab58d9d1cdc3e1347077f531ad0339797568345464f542f8da3bcd50fd683878f52e6d094610025d6e4a5fb3699acd20ebd1ec2fdde9d12f5e82fe5f4c8d9061466475b3293bb18c34504c6eb43bc0ba48d61a8edfda686c69773fa96b90d00760d8277330d90589ba26fb63874952b013a8af1a5edacbcabb37108b47518c79abd6e50be00da0a08fb9126fd265175cace1ac93d1f809b80",
"data": "~Ǹǜ舚~Ǥƅ~ĎǦ늀~ĀƂ刈Ɩ쭥홷㡗υ⡒缪༎䀛䫱颍釅襮㽏⪲ᡅ~ĀĀ肸ꯏ晴缴~Ǹǜ趋즥ඥ~ǞǷ蚡滊떍鴜~ǜľፇݿ匚퀳鞗嚃䕆佔⾍ꎼ픏횃螏勦킔愀◖~Ǥƥזּ馬툎봞싽~ǞƝድ~Ǩį~ǥǴ죙ؔ晇嬲鎻ᣃ䔄웫䎼த赡꣭ﶦ蛆靳殺뤍vං眳ඐ墛ꉯ똸璕⬁㪊~DZƥ~ǭƬ벫덱ࢴ甘잚뵮傾Úਈﮑ⛽♑痊츚줽ᾀ鮀",
"description": "Correct transaction without 0x prefix."
"description": "Correct transaction without 0x prefix.",
"length": 139,
"sms": 3,
"mms": 1
}
],
"invalid": [
Expand Down