1 | import cloneDeep from 'lodash.clonedeep';
|
2 |
|
3 | import userAgents from './user-agents.json';
|
4 |
|
5 |
|
6 |
|
7 | const makeCumulativeWeightIndexPairs = (weightIndexPairs) => {
|
8 | const totalWeight = weightIndexPairs.reduce((sum, [weight]) => sum + weight, 0);
|
9 | let sum = 0;
|
10 | return weightIndexPairs.map(([weight, index]) => {
|
11 | sum += weight / totalWeight;
|
12 | return [sum, index];
|
13 | });
|
14 | };
|
15 |
|
16 |
|
17 | const defaultWeightIndexPairs = userAgents.map(({ weight }, index) => [weight, index]);
|
18 | const defaultCumulativeWeightIndexPairs = makeCumulativeWeightIndexPairs(defaultWeightIndexPairs);
|
19 |
|
20 |
|
21 |
|
22 | const constructFilter = (filters, accessor = parentObject => parentObject) => {
|
23 | let childFilters;
|
24 | if (typeof filters === 'function') {
|
25 | childFilters = [filters];
|
26 | } else if (filters instanceof RegExp) {
|
27 | childFilters = [
|
28 | value => (
|
29 | typeof value === 'object' && value && value.userAgent
|
30 | ? filters.test(value.userAgent)
|
31 | : filters.test(value)
|
32 | ),
|
33 | ];
|
34 | } else if (filters instanceof Array) {
|
35 | childFilters = filters.map(childFilter => constructFilter(childFilter));
|
36 | } else if (typeof filters === 'object') {
|
37 | childFilters = Object.entries(filters).map(([key, valueFilter]) => (
|
38 | constructFilter(valueFilter, parentObject => parentObject[key])
|
39 | ));
|
40 | } else {
|
41 | childFilters = [
|
42 | value => (
|
43 | typeof value === 'object' && value && value.userAgent
|
44 | ? filters === value.userAgent
|
45 | : filters === value
|
46 | ),
|
47 | ];
|
48 | }
|
49 |
|
50 | return (parentObject) => {
|
51 | try {
|
52 | const value = accessor(parentObject);
|
53 | return childFilters.every(childFilter => childFilter(value));
|
54 | } catch (error) {
|
55 |
|
56 | return false;
|
57 | }
|
58 | };
|
59 | };
|
60 |
|
61 |
|
62 |
|
63 | const constructCumulativeWeightIndexPairsFromFilters = (filters) => {
|
64 | if (!filters) {
|
65 | return defaultCumulativeWeightIndexPairs;
|
66 | }
|
67 |
|
68 | const filter = constructFilter(filters);
|
69 |
|
70 | const weightIndexPairs = [];
|
71 | userAgents.forEach((rawUserAgent, index) => {
|
72 | if (filter(rawUserAgent)) {
|
73 | weightIndexPairs.push([rawUserAgent.weight, index]);
|
74 | }
|
75 | });
|
76 | return makeCumulativeWeightIndexPairs(weightIndexPairs);
|
77 | };
|
78 |
|
79 |
|
80 | const setCumulativeWeightIndexPairs = (userAgent, cumulativeWeightIndexPairs) => {
|
81 | Object.defineProperty(userAgent, 'cumulativeWeightIndexPairs', {
|
82 | configurable: true,
|
83 | enumerable: false,
|
84 | writable: false,
|
85 | value: cumulativeWeightIndexPairs,
|
86 | });
|
87 | };
|
88 |
|
89 |
|
90 | export default class UserAgent extends Function {
|
91 | constructor(filters) {
|
92 | super();
|
93 | setCumulativeWeightIndexPairs(this, constructCumulativeWeightIndexPairsFromFilters(filters));
|
94 | if (this.cumulativeWeightIndexPairs.length === 0) {
|
95 | throw new Error('No user agents matched your filters.');
|
96 | }
|
97 |
|
98 | this.randomize();
|
99 |
|
100 | return new Proxy(this, {
|
101 | apply: () => this.random(),
|
102 | get: (target, property, receiver) => {
|
103 | const dataCandidate = target.data && typeof property === 'string'
|
104 | && Object.prototype.hasOwnProperty.call(target.data, property)
|
105 | && Object.prototype.propertyIsEnumerable.call(target.data, property);
|
106 | if (dataCandidate) {
|
107 | const value = target.data[property];
|
108 | if (value !== undefined) {
|
109 | return value;
|
110 | }
|
111 | }
|
112 |
|
113 | return Reflect.get(target, property, receiver);
|
114 | },
|
115 | });
|
116 | }
|
117 |
|
118 | static random = (filters) => {
|
119 | try {
|
120 | return new UserAgent(filters);
|
121 | } catch (error) {
|
122 | return null;
|
123 | }
|
124 | };
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 | [Symbol.toPrimitive] = () => (
|
131 | this.data.userAgent
|
132 | );
|
133 |
|
134 | toString = () => (
|
135 | this.data.userAgent
|
136 | );
|
137 |
|
138 | random = () => {
|
139 | const userAgent = new UserAgent();
|
140 | setCumulativeWeightIndexPairs(userAgent, this.cumulativeWeightIndexPairs);
|
141 | userAgent.randomize();
|
142 | return userAgent;
|
143 | };
|
144 |
|
145 | randomize = () => {
|
146 |
|
147 | const randomNumber = Math.random();
|
148 | const [, index] = this.cumulativeWeightIndexPairs
|
149 | .find(([cumulativeWeight]) => cumulativeWeight > randomNumber);
|
150 | const rawUserAgent = userAgents[index];
|
151 |
|
152 | this.data = cloneDeep(rawUserAgent);
|
153 | }
|
154 | }
|