make-mp-data 1.0.15 โ 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +4 -1
- package/cli.js +8 -1
- package/default.js +7 -6
- package/e2e.test.js +50 -0
- package/index.js +36 -16
- package/package.json +4 -2
- package/types.d.ts +53 -0
- package/utils.js +56 -11
package/.vscode/settings.json
CHANGED
package/cli.js
CHANGED
|
@@ -25,38 +25,45 @@ DOCS: https://github.com/ak--47/make-mp-data`)
|
|
|
25
25
|
.command('$0', 'model mixpanel data', () => { })
|
|
26
26
|
.option("token", {
|
|
27
27
|
demandOption: false,
|
|
28
|
+
alias: 't',
|
|
28
29
|
describe: 'project token; if supplied data will be sent to mixpanel',
|
|
29
30
|
type: 'string'
|
|
30
31
|
})
|
|
31
32
|
.option("seed", {
|
|
32
|
-
demandOption: false,
|
|
33
|
+
demandOption: false,
|
|
34
|
+
alias: 's',
|
|
33
35
|
describe: 'randomness seed; used to create distinct_ids',
|
|
34
36
|
type: 'string'
|
|
35
37
|
})
|
|
36
38
|
.option("format", {
|
|
37
39
|
demandOption: false,
|
|
38
40
|
default: 'csv',
|
|
41
|
+
alias: 'f',
|
|
39
42
|
describe: 'csv or json',
|
|
40
43
|
type: 'string'
|
|
41
44
|
})
|
|
42
45
|
.option("numDays", {
|
|
43
46
|
demandOption: false,
|
|
47
|
+
alias: 'd',
|
|
44
48
|
describe: 'number of days in past to model',
|
|
45
49
|
type: 'number',
|
|
46
50
|
})
|
|
47
51
|
.option("numUsers", {
|
|
48
52
|
demandOption: false,
|
|
53
|
+
alias: 'u',
|
|
49
54
|
describe: 'number of users to model',
|
|
50
55
|
type: 'number',
|
|
51
56
|
})
|
|
52
57
|
.option("numEvents", {
|
|
53
58
|
demandOption: false,
|
|
59
|
+
alias: 'e',
|
|
54
60
|
describe: 'number of events to model',
|
|
55
61
|
type: 'number',
|
|
56
62
|
})
|
|
57
63
|
.option("region", {
|
|
58
64
|
demandOption: false,
|
|
59
65
|
default: 'US',
|
|
66
|
+
alias: 'r',
|
|
60
67
|
describe: 'either US or EU',
|
|
61
68
|
type: 'string'
|
|
62
69
|
})
|
package/default.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const Chance = require('chance');
|
|
2
2
|
const chance = new Chance();
|
|
3
|
-
const { weightedRange, makeProducts, date } = require('./utils.js');
|
|
3
|
+
const { weightedRange, makeProducts, date, generateEmoji } = require('./utils.js');
|
|
4
4
|
|
|
5
5
|
const config = {
|
|
6
6
|
token: "",
|
|
@@ -36,7 +36,8 @@ const config = {
|
|
|
36
36
|
"event": "page view",
|
|
37
37
|
"weight": 10,
|
|
38
38
|
"properties": {
|
|
39
|
-
|
|
39
|
+
page: ["/", "/", "/help", "/account", "/watch", "/listen", "/product", "/people", "/peace"],
|
|
40
|
+
utm_source: ["$organic", "$organic", "$organic", "$organic", "google", "google", "google", "facebook", "facebook", "twitter", "linkedin"],
|
|
40
41
|
}
|
|
41
42
|
},
|
|
42
43
|
{
|
|
@@ -66,7 +67,8 @@ const config = {
|
|
|
66
67
|
}
|
|
67
68
|
],
|
|
68
69
|
superProps: {
|
|
69
|
-
platform: ["web", "mobile", "kiosk"],
|
|
70
|
+
platform: ["web", "mobile", "web", "mobile", "web", "kiosk"],
|
|
71
|
+
emotions: generateEmoji(),
|
|
70
72
|
|
|
71
73
|
},
|
|
72
74
|
/*
|
|
@@ -76,17 +78,16 @@ const config = {
|
|
|
76
78
|
userProps: {
|
|
77
79
|
title: chance.profession.bind(chance),
|
|
78
80
|
luckyNumber: weightedRange(42, 420),
|
|
79
|
-
|
|
81
|
+
vibe: generateEmoji(),
|
|
80
82
|
spiritAnimal: chance.animal.bind(chance)
|
|
81
83
|
},
|
|
82
84
|
|
|
83
85
|
scdProps: {
|
|
84
|
-
plan: ["free", "free", "free", "basic", "basic", "premium", "enterprise"],
|
|
86
|
+
plan: ["free", "free", "free", "free", "basic", "basic", "basic", "premium", "premium", "enterprise"],
|
|
85
87
|
MRR: weightedRange(0, 10000, 1000, .15),
|
|
86
88
|
NPS: weightedRange(0, 10, 150, 2),
|
|
87
89
|
marketingOptIn: [true, true, false],
|
|
88
90
|
dateOfRenewal: date(100, false),
|
|
89
|
-
|
|
90
91
|
},
|
|
91
92
|
|
|
92
93
|
/*
|
package/e2e.test.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* cSpell:disable */
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
/* eslint-disable no-undef */
|
|
4
|
+
/* eslint-disable no-debugger */
|
|
5
|
+
/* eslint-disable no-unused-vars */
|
|
6
|
+
const { generate } = require('./index.js');
|
|
7
|
+
require('dotenv').config();
|
|
8
|
+
const { execSync } = require("child_process");
|
|
9
|
+
const u = require('ak-tools');
|
|
10
|
+
|
|
11
|
+
const timeout = 60000;
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
describe('e2e', () => {
|
|
15
|
+
|
|
16
|
+
test('works as module', async () => {
|
|
17
|
+
console.log('MODULE TEST');
|
|
18
|
+
const results = await generate({ writeToDisk: false, numEvents: 1000, numUsers: 100, seed: "deal with it" });
|
|
19
|
+
const { eventData, groupProfilesData, lookupTableData, scdTableData, userProfilesData } = results;
|
|
20
|
+
expect(eventData.length).toBeGreaterThan(900);
|
|
21
|
+
expect(groupProfilesData.length).toBe(0);
|
|
22
|
+
expect(lookupTableData.length).toBe(0);
|
|
23
|
+
expect(scdTableData.length).toBeGreaterThan(200);
|
|
24
|
+
expect(userProfilesData.length).toBe(100);
|
|
25
|
+
|
|
26
|
+
}, timeout);
|
|
27
|
+
|
|
28
|
+
test('works as CLI', async () => {
|
|
29
|
+
console.log('CLI TEST');
|
|
30
|
+
const run = execSync(`node ./index.js --numEvents 1000 --numUsers 100 --seed "deal with it"`);
|
|
31
|
+
expect(run.toString().trim().includes('have a wonderful day :)')).toBe(true);
|
|
32
|
+
const csvs = (await u.ls('./data')).filter(a => a.includes('.csv'));
|
|
33
|
+
expect(csvs.length).toBe(5);
|
|
34
|
+
}, timeout);
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterAll(() => {
|
|
47
|
+
console.log('clearing...');
|
|
48
|
+
execSync(`npm run prune`);
|
|
49
|
+
console.log('...files cleared ๐');
|
|
50
|
+
});
|
package/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#! /usr/bin/env node
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
/*
|
|
4
5
|
make fake mixpanel data easily!
|
|
5
6
|
by AK
|
|
6
7
|
ak@mixpanel.com
|
|
7
8
|
*/
|
|
8
9
|
|
|
10
|
+
const RUNTIME = process.env.RUNTIME || "unspecified";
|
|
9
11
|
const mp = require("mixpanel-import");
|
|
10
12
|
const path = require("path");
|
|
11
13
|
const Chance = require("chance");
|
|
@@ -27,14 +29,18 @@ const {
|
|
|
27
29
|
openFinder,
|
|
28
30
|
applySkew,
|
|
29
31
|
boxMullerRandom,
|
|
32
|
+
getUniqueKeys
|
|
30
33
|
} = require("./utils.js");
|
|
31
34
|
const dayjs = require("dayjs");
|
|
32
35
|
const utc = require("dayjs/plugin/utc");
|
|
33
36
|
dayjs.extend(utc);
|
|
34
37
|
const cliParams = require("./cli.js");
|
|
38
|
+
// @ts-ignore
|
|
35
39
|
Array.prototype.pickOne = pick;
|
|
36
40
|
const NOW = dayjs().unix();
|
|
37
41
|
|
|
42
|
+
/** @typedef {import('./types.d.ts').Config} Config */
|
|
43
|
+
|
|
38
44
|
const PEAK_DAYS = [
|
|
39
45
|
dayjs().subtract(2, "day").unix(),
|
|
40
46
|
dayjs().subtract(3, "day").unix(),
|
|
@@ -48,7 +54,10 @@ const PEAK_DAYS = [
|
|
|
48
54
|
dayjs().subtract(29, "day").unix(),
|
|
49
55
|
];
|
|
50
56
|
|
|
51
|
-
|
|
57
|
+
/**
|
|
58
|
+
* generates fake mixpanel data
|
|
59
|
+
* @param {Config} config
|
|
60
|
+
*/
|
|
52
61
|
async function main(config) {
|
|
53
62
|
let {
|
|
54
63
|
seed = "every time a rug is micturated upon in this fair city...",
|
|
@@ -70,29 +79,30 @@ async function main(config) {
|
|
|
70
79
|
region = "US",
|
|
71
80
|
writeToDisk = false,
|
|
72
81
|
} = config;
|
|
73
|
-
|
|
82
|
+
|
|
74
83
|
//ensure we have a token or are writing to disk
|
|
75
84
|
if (require.main === module) {
|
|
76
85
|
if (!token) {
|
|
77
86
|
if (!writeToDisk) {
|
|
78
87
|
writeToDisk = true;
|
|
88
|
+
config.writeToDisk = true;
|
|
79
89
|
}
|
|
80
90
|
}
|
|
81
91
|
}
|
|
82
|
-
|
|
92
|
+
|
|
83
93
|
const uuidChance = new Chance(seed);
|
|
84
94
|
|
|
85
|
-
//the function which generates $distinct_id + $created
|
|
95
|
+
//the function which generates $distinct_id + $created, skewing towards the present
|
|
86
96
|
function uuid() {
|
|
87
97
|
const distinct_id = uuidChance.guid();
|
|
88
98
|
let z = boxMullerRandom();
|
|
89
|
-
const skew = chance.normal({ mean: 10, dev: 3 });
|
|
90
|
-
z = applySkew(z, skew);
|
|
99
|
+
const skew = chance.normal({ mean: 10, dev: 3 });
|
|
100
|
+
z = applySkew(z, skew);
|
|
91
101
|
|
|
92
102
|
// Scale and shift the normally distributed value to fit the range of days
|
|
93
|
-
const maxZ = integer(2, 4);
|
|
94
|
-
const scaledZ = (z / maxZ + 1) / 2;
|
|
95
|
-
const daysAgoBorn = Math.round(scaledZ * (numDays - 1)) + 1;
|
|
103
|
+
const maxZ = integer(2, 4);
|
|
104
|
+
const scaledZ = (z / maxZ + 1) / 2;
|
|
105
|
+
const daysAgoBorn = Math.round(scaledZ * (numDays - 1)) + 1;
|
|
96
106
|
|
|
97
107
|
return {
|
|
98
108
|
distinct_id,
|
|
@@ -223,7 +233,14 @@ async function main(config) {
|
|
|
223
233
|
for (const writeData of datasetsToWrite) {
|
|
224
234
|
if (format === "csv") {
|
|
225
235
|
console.log(`writing ${path}`);
|
|
226
|
-
const
|
|
236
|
+
const columns = getUniqueKeys(writeData);
|
|
237
|
+
//papa parse needs nested JSON stringified
|
|
238
|
+
writeData.forEach((e) => {
|
|
239
|
+
for (const key in e) {
|
|
240
|
+
if (typeof e[key] === "object") e[key] = JSON.stringify(e[key]);
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
const csv = Papa.unparse(writeData, { columns });
|
|
227
244
|
await touch(path, csv);
|
|
228
245
|
console.log(`\tdone\n`);
|
|
229
246
|
} else {
|
|
@@ -238,13 +255,13 @@ async function main(config) {
|
|
|
238
255
|
const creds = { token };
|
|
239
256
|
/** @type {import('mixpanel-import').Options} */
|
|
240
257
|
const importOpts = {
|
|
258
|
+
region,
|
|
241
259
|
fixData: true,
|
|
242
260
|
verbose: false,
|
|
243
261
|
forceStream: true,
|
|
244
262
|
strict: false,
|
|
245
263
|
dryRun: false,
|
|
246
264
|
abridged: false,
|
|
247
|
-
region,
|
|
248
265
|
};
|
|
249
266
|
//send to mixpanel
|
|
250
267
|
if (token) {
|
|
@@ -252,6 +269,9 @@ async function main(config) {
|
|
|
252
269
|
console.log(`importing events to mixpanel...`);
|
|
253
270
|
const imported = await mp(creds, eventData, {
|
|
254
271
|
recordType: "event",
|
|
272
|
+
fixData: true,
|
|
273
|
+
fixJson: true,
|
|
274
|
+
strict: false,
|
|
255
275
|
...importOpts,
|
|
256
276
|
});
|
|
257
277
|
console.log(`\tsent ${comma(imported.success)} events\n`);
|
|
@@ -331,10 +351,10 @@ function makeEvent(distinct_id, earliestTime, events, superProps, groupKeys, isF
|
|
|
331
351
|
const event = {
|
|
332
352
|
event: chosenEvent.event,
|
|
333
353
|
distinct_id,
|
|
334
|
-
$source: "
|
|
354
|
+
$source: "AKsTimeSoup",
|
|
335
355
|
};
|
|
336
356
|
|
|
337
|
-
if (isFirstEvent) event.time = earliestTime;
|
|
357
|
+
if (isFirstEvent) event.time = dayjs.unix(earliestTime).toISOString();
|
|
338
358
|
if (!isFirstEvent) event.time = AKsTimeSoup(earliestTime, NOW, PEAK_DAYS);
|
|
339
359
|
|
|
340
360
|
const props = { ...chosenEvent.properties, ...superProps };
|
|
@@ -392,7 +412,7 @@ function buildFileNames(config) {
|
|
|
392
412
|
}
|
|
393
413
|
|
|
394
414
|
/**
|
|
395
|
-
* timestamp generator with a twist
|
|
415
|
+
* essentially, a timestamp generator with a twist
|
|
396
416
|
* @param {number} earliestTime - The earliest timestamp in Unix format.
|
|
397
417
|
* @param {number} latestTime - The latest timestamp in Unix format.
|
|
398
418
|
* @param {Array} peakDays - Array of Unix timestamps representing the start of peak days.
|
|
@@ -432,8 +452,8 @@ function AKsTimeSoup(earliestTime, latestTime = NOW, peakDays = PEAK_DAYS) {
|
|
|
432
452
|
|
|
433
453
|
// usually, ensure the event time is within business hours
|
|
434
454
|
if (chance.bool({ likelihood: 42 })) eventTime = Math.min(Math.max(eventTime, businessStart), businessEnd);
|
|
435
|
-
|
|
436
|
-
return eventTime;
|
|
455
|
+
|
|
456
|
+
return dayjs.unix(eventTime).toISOString();
|
|
437
457
|
}
|
|
438
458
|
|
|
439
459
|
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "make-mp-data",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
4
4
|
"description": "builds all mixpanel primitives for a given project",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"types": "types.d.ts",
|
|
6
7
|
"scripts": {
|
|
7
8
|
"start": "node index.js",
|
|
8
9
|
"prune": "rm ./data/*",
|
|
9
10
|
"go": "sh ./go.sh",
|
|
10
|
-
"post": "npm publish"
|
|
11
|
+
"post": "npm publish",
|
|
12
|
+
"test": "jest"
|
|
11
13
|
},
|
|
12
14
|
"repository": {
|
|
13
15
|
"type": "git",
|
package/types.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// types.d.ts
|
|
2
|
+
import { Chance } from "chance";
|
|
3
|
+
|
|
4
|
+
export interface Config {
|
|
5
|
+
token?: string;
|
|
6
|
+
seed?: string;
|
|
7
|
+
numDays?: number;
|
|
8
|
+
numEvents?: number;
|
|
9
|
+
numUsers?: number;
|
|
10
|
+
format?: "csv" | "json";
|
|
11
|
+
region?: string;
|
|
12
|
+
events?: EventConfig[];
|
|
13
|
+
superProps?: Record<string, string[]>; // Flexible for any string keys
|
|
14
|
+
userProps?: Record<string, any>; // Could be more specific based on actual usage
|
|
15
|
+
scdProps?: {
|
|
16
|
+
plan?: string[];
|
|
17
|
+
MRR?: number;
|
|
18
|
+
NPS?: number;
|
|
19
|
+
marketingOptIn?: boolean[];
|
|
20
|
+
dateOfRenewal?: Date;
|
|
21
|
+
};
|
|
22
|
+
groupKeys?: [string, number][];
|
|
23
|
+
groupProps?: Record<string, GroupProperty>; // Adjust according to usage
|
|
24
|
+
lookupTables?: LookupTable[];
|
|
25
|
+
writeToDisk?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface EventConfig {
|
|
29
|
+
event?: string;
|
|
30
|
+
weight?: number;
|
|
31
|
+
properties?: {
|
|
32
|
+
[key: string]: any; // Consider refining based on actual properties used
|
|
33
|
+
};
|
|
34
|
+
isFirstEvent?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface GroupProperty {
|
|
38
|
+
[key?: string]: any;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface LookupTable {
|
|
42
|
+
key?: string;
|
|
43
|
+
entries?: number;
|
|
44
|
+
attributes?: {
|
|
45
|
+
category?: string[];
|
|
46
|
+
demand?: string[];
|
|
47
|
+
supply?: string[];
|
|
48
|
+
manufacturer?: () => string;
|
|
49
|
+
price?: number;
|
|
50
|
+
rating?: number;
|
|
51
|
+
reviews?: number;
|
|
52
|
+
};
|
|
53
|
+
}
|
package/utils.js
CHANGED
|
@@ -20,12 +20,28 @@ function pick() {
|
|
|
20
20
|
function date(inTheLast = 30, isPast = true, format = 'YYYY-MM-DD') {
|
|
21
21
|
const now = dayjs.utc();
|
|
22
22
|
return function () {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
try {
|
|
24
|
+
const when = chance.integer({ min: 0, max: Math.abs(inTheLast) });
|
|
25
|
+
let then;
|
|
26
|
+
if (isPast) {
|
|
27
|
+
then = now.subtract(when, 'day')
|
|
28
|
+
.subtract(integer(0, 23), 'hour')
|
|
29
|
+
.subtract(integer(0, 59), 'minute')
|
|
30
|
+
.subtract(integer(0, 59), 'second');
|
|
31
|
+
}
|
|
32
|
+
if (!isPast) {
|
|
33
|
+
then = now.add(when, 'day')
|
|
34
|
+
.add(integer(0, 23), 'hour')
|
|
35
|
+
.add(integer(0, 59), 'minute')
|
|
36
|
+
.add(integer(0, 59), 'second');
|
|
37
|
+
}
|
|
38
|
+
if (format) return then?.format(format);
|
|
39
|
+
if (!format) return then?.toISOString();
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
if (format) return now?.format(format);
|
|
43
|
+
if (!format) return now?.toISOString();
|
|
44
|
+
}
|
|
29
45
|
};
|
|
30
46
|
}
|
|
31
47
|
|
|
@@ -74,6 +90,10 @@ function exhaust(arr) {
|
|
|
74
90
|
|
|
75
91
|
|
|
76
92
|
function integer(min, max) {
|
|
93
|
+
if (min === max) {
|
|
94
|
+
return min;
|
|
95
|
+
}
|
|
96
|
+
|
|
77
97
|
if (min > max) {
|
|
78
98
|
return chance.integer({
|
|
79
99
|
min: max,
|
|
@@ -81,10 +101,14 @@ function integer(min, max) {
|
|
|
81
101
|
});
|
|
82
102
|
}
|
|
83
103
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
104
|
+
if (min < max) {
|
|
105
|
+
return chance.integer({
|
|
106
|
+
min: min,
|
|
107
|
+
max: max
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return 0;
|
|
88
112
|
}
|
|
89
113
|
|
|
90
114
|
function makeProducts() {
|
|
@@ -168,7 +192,6 @@ function progress(thing, p) {
|
|
|
168
192
|
process.stdout.write(`${thing} processed ... ${comma(p)}`);
|
|
169
193
|
}
|
|
170
194
|
|
|
171
|
-
|
|
172
195
|
function person(bornDaysAgo = 30) {
|
|
173
196
|
//names and photos
|
|
174
197
|
const gender = chance.pickone(['male', 'female']);
|
|
@@ -213,7 +236,27 @@ function openFinder(path, callback) {
|
|
|
213
236
|
});
|
|
214
237
|
}
|
|
215
238
|
|
|
239
|
+
function getUniqueKeys(data) {
|
|
240
|
+
const keysSet = new Set();
|
|
241
|
+
data.forEach(item => {
|
|
242
|
+
Object.keys(item).forEach(key => keysSet.add(key));
|
|
243
|
+
});
|
|
244
|
+
return Array.from(keysSet);
|
|
245
|
+
}
|
|
216
246
|
|
|
247
|
+
//makes a random-sized array of emojis
|
|
248
|
+
function generateEmoji(max = 10, array = false) {
|
|
249
|
+
return function () {
|
|
250
|
+
const emojis = ['๐', '๐', '๐', '๐', '๐', '๐', '๐ก', '๐ฑ', '๐ญ', '๐ด', '๐คข', '๐ค ', '๐คก', '๐ฝ', '๐ป', '๐ฉ', '๐บ', '๐น', '๐พ', '๐ค', '๐ค', '๐ค', '๐ค', '๐ค', '๐ค', '๐', '๐', '๐', '๐', '๐', '๐', '๐ก', '๐ฑ', '๐ญ', '๐ด', '๐คข', '๐ค ', '๐คก', '๐ฝ', '๐ป', '๐ฉ', '๐บ', '๐น', '๐พ', '๐ค', '๐ค', '๐ค', '๐ค', '๐ค', '๐ค', '๐', '๐ฟ', '๐ฆ', '๐ง', '๐จ', '๐ฉ', '๐ด', '๐ต', '๐ถ', '๐ง', '๐ฎ', '๐ท', '๐', '๐ต', '๐ฉโโ๏ธ', '๐จโโ๏ธ', '๐ฉโ๐พ', '๐จโ๐พ', '๐ฉโ๐ณ', '๐จโ๐ณ', '๐ฉโ๐', '๐จโ๐', '๐ฉโ๐ค', '๐จโ๐ค', '๐ฉโ๐ซ', '๐จโ๐ซ', '๐ฉโ๐ญ', '๐จโ๐ญ', '๐ฉโ๐ป', '๐จโ๐ป', '๐ฉโ๐ผ', '๐จโ๐ผ', '๐ฉโ๐ง', '๐จโ๐ง', '๐ฉโ๐ฌ', '๐จโ๐ฌ', '๐ฉโ๐จ', '๐จโ๐จ', '๐ฉโ๐', '๐จโ๐', '๐ฉโโ๏ธ', '๐จโโ๏ธ', '๐ฉโ๐', '๐จโ๐', '๐ฉโโ๏ธ', '๐จโโ๏ธ', '๐คถ', '๐
', '๐ธ', '๐คด', '๐ฐ', '๐คต', '๐ผ', '๐คฐ', '๐', '๐', '๐
', '๐', '๐', '๐คฆ', '๐คท', '๐', '๐', '๐', '๐', '๐ด', '๐', '๐บ', '๐ถ', '๐', '๐คฒ', '๐', '๐', '๐', '๐ค', '๐', '๐', '๐', 'โ', '๐ค', '๐ค', '๐ค', 'โ๏ธ', '๐ค', '๐ค', '๐', '๐', '๐', '๐', '๐', 'โ๏ธ', 'โ', '๐ค', '๐', '๐', '๐', '๐ค', '๐ช', '๐', 'โ๏ธ', '๐คณ', '๐
', '๐', '๐', '๐ฃ', '๐', '๐', '๐ง ', '๐
', '๐', '๐', '๐', '๐ถ', '๐', '๐', '๐', '๐งฃ', '๐งค', '๐งฅ', '๐งฆ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐ ', '๐ก', '๐ข', '๐', '๐', '๐ฉ', '๐', '๐งข', 'โ', '๐ฟ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐ข', '๐ฃ', '๐ฏ', '๐', '๐', '๐ผ', '๐ต', '๐ถ', '๐', '๐', '๐', '๐ค', '๐ง', '๐ป', '๐ท', '๐ธ', '๐น', '๐บ', '๐ป', '๐ฅ', '๐ฑ', '๐ฒ', '๐ป', '๐ฅ', '๐จ', '๐ฑ', '๐ฒ', '๐น', '๐', '๐ฝ', '๐พ', '๐ฟ', '๐', '๐ผ', '๐ท', '๐ธ', '๐น', '๐ฅ', '๐ฝ', '๐', '๐', 'โ๏ธ', '๐', '๐ ', '๐บ', '๐ป', '๐', '๐ก', '๐', '๐', '๐ฌ', '๐ญ', '๐ก', '๐ก', '๐ฆ', '๐ฎ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐ฐ', '๐', '๐', '๐', '๐ท', '๐ฐ', '๐ด', '๐ต', '๐ถ', '๐ท', '๐ธ', '๐ณ', '๐งพ', '๐น', '๐ฑ', '๐ฒ', 'โ๏ธ', '๐ง', '๐จ', '๐ฉ', '๐ค', '๐ฅ', '๐ฆ', '๐ซ', '๐ช', '๐ฌ', '๐ญ', '๐ฎ', '๐ณ', 'โ๏ธ', 'โ๏ธ', '๐', '๐', '๐', '๐', '๐', '๐ผ', '๐', '๐', '๐', '๐
', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', 'โ๏ธ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐จ', 'โ', 'โ', '๐ ', '๐ก', 'โ๏ธ', '๐ซ', '๐น', '๐ก', '๐ง', '๐ฉ', 'โ๏ธ', '๐', 'โ๏ธ', '๐', 'โ', '๐งฐ', '๐งฒ', 'โ๏ธ', '๐งช', '๐งซ', '๐งฌ', '๐ฌ', '๐ญ', '๐ก', '๐', '๐', '๐', '๐', '๐ช', '๐ฝ', '๐ฟ', '๐', '๐งด', '๐งท', '๐งน', '๐งบ', '๐งป', '๐งผ', '๐งฝ', '๐งฏ', '๐ฌ', 'โฐ๏ธ', 'โฑ๏ธ', '๐ฟ', '๐บ', '๐งฑ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐ฎ', '๐', '๐งง', 'โ๏ธ', '๐ฉ', '๐จ', '๐ง'];
|
|
251
|
+
let num = integer(1, max);
|
|
252
|
+
let arr = [];
|
|
253
|
+
for (let i = 0; i < num; i++) {
|
|
254
|
+
arr.push(chance.pickone(emojis));
|
|
255
|
+
}
|
|
256
|
+
if (array) return arr;
|
|
257
|
+
if (!array) return arr.join(', ');
|
|
258
|
+
};
|
|
259
|
+
}
|
|
217
260
|
|
|
218
261
|
module.exports = {
|
|
219
262
|
weightedRange,
|
|
@@ -230,4 +273,6 @@ module.exports = {
|
|
|
230
273
|
openFinder,
|
|
231
274
|
applySkew,
|
|
232
275
|
boxMullerRandom,
|
|
276
|
+
generateEmoji,
|
|
277
|
+
getUniqueKeys
|
|
233
278
|
};
|