UNPKG

4.48 kBJavaScriptView Raw
1import cloneDeep from 'lodash.clonedeep';
2
3import userAgents from './user-agents.json';
4
5
6// Normalizes the total weight to 1 and constructs a cumulative distribution.
7const 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// Precompute these so that we can quickly generate unfiltered user agents.
17const defaultWeightIndexPairs = userAgents.map(({ weight }, index) => [weight, index]);
18const defaultCumulativeWeightIndexPairs = makeCumulativeWeightIndexPairs(defaultWeightIndexPairs);
19
20
21// Turn the various filter formats into a single filter function that acts on raw user agents.
22const 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 // This happens when a user-agent lacks a nested property.
56 return false;
57 }
58 };
59};
60
61
62// Construct normalized cumulative weight index pairs given the filters.
63const 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
80const setCumulativeWeightIndexPairs = (userAgent, cumulativeWeightIndexPairs) => {
81 Object.defineProperty(userAgent, 'cumulativeWeightIndexPairs', {
82 configurable: true,
83 enumerable: false,
84 writable: false,
85 value: cumulativeWeightIndexPairs,
86 });
87};
88
89
90export 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 // Standard Object Methods
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 // Find a random raw random user agent.
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}