@cavoq/mcc-mnc-list 1.2.2

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.
@@ -0,0 +1,43 @@
1
+ name: CI + Release
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
8
+ release:
9
+ types: [published]
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ node-version: [20.x, 22.x]
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ - uses: actions/setup-node@v4
21
+ with:
22
+ node-version: ${{ matrix.node-version }}
23
+ cache: npm
24
+ - run: npm ci
25
+ - run: npm test
26
+
27
+ publish-npm:
28
+ if: github.event_name == 'release'
29
+ needs: test
30
+ runs-on: ubuntu-latest
31
+ permissions:
32
+ contents: read
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+ - uses: actions/setup-node@v4
36
+ with:
37
+ node-version: 20.x
38
+ registry-url: https://registry.npmjs.org/
39
+ cache: npm
40
+ - run: npm ci
41
+ - run: npm publish --access public
42
+ env:
43
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/Dockerfile ADDED
@@ -0,0 +1,19 @@
1
+ FROM node:22-bookworm-slim
2
+
3
+ ENV NODE_ENV=production \
4
+ NPM_CONFIG_AUDIT=false \
5
+ NPM_CONFIG_FUND=false \
6
+ NPM_CONFIG_UPDATE_NOTIFIER=false
7
+
8
+ WORKDIR /mnc-mcc-list
9
+
10
+ COPY package*.json ./
11
+
12
+ # `fetch.js` requires `jsdom`, which currently lives in devDependencies.
13
+ RUN npm install --include=dev --omit=optional && npm cache clean --force
14
+
15
+ COPY --chown=node:node . .
16
+
17
+ USER node
18
+
19
+ CMD ["node", "fetch.js"]
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Peter Bakondy
4
+ Copyright (c) 2026 David Stromberger
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # mcc-mnc-list
2
+
3
+ [![CI](https://github.com/cavoq/mcc-mnc-list/actions/workflows/release.yml/badge.svg)](https://github.com/cavoq/mcc-mnc-list/actions/workflows/release.yml)
4
+ [![npm version](https://img.shields.io/npm/v/%40cavoq%2Fmcc-mnc-list.svg)](https://www.npmjs.com/package/@cavoq/mcc-mnc-list)
5
+
6
+ ### 🌍 List of MCC and MNC codes from the up-to-date Wikipedia page
7
+
8
+ Maintained fork of the original `mcc-mnc-list` package.
9
+
10
+ Source: https://en.wikipedia.org/wiki/Mobile_country_code
11
+
12
+ ## Definition
13
+
14
+ The ITU-T Recommendation E.212 defines mobile country codes as well as mobile network codes. The mobile country code consists of 3 decimal digits and the mobile network code consists of 2 or 3 decimal digits (for example: MNC of 001 is not the same as MNC of 01). The first digit of the mobile country code identifies the geographic region as follows (the digits 1 and 8 are not used):
15
+
16
+ - `0` - Test networks
17
+ - `2` - Europe
18
+ - `3` - North America and the Caribbean
19
+ - `4` - Asia and the Middle East
20
+ - `5` - Oceania
21
+ - `6` - Africa
22
+ - `7` - South and Central America
23
+ - `9` - World-wide (Satellite, Air - aboard aircraft, Maritime - aboard ships, Antarctica)
24
+
25
+ A mobile country code (MCC) is used in combination with a mobile network code (MNC) (also known as a "MCC / MNC tuple") to uniquely identify a mobile network operator (carrier) using the GSM (including GSM-R), UMTS, and LTE public land mobile networks. (*source: Wikipedia*)
26
+
27
+ ## 📦 Install
28
+
29
+ ```
30
+ $ npm install @cavoq/mcc-mnc-list
31
+ ```
32
+
33
+ ## 🐳 Docker
34
+
35
+ Build the image:
36
+
37
+ ```bash
38
+ docker build -t mcc-mnc-list .
39
+ ```
40
+
41
+ Run the fetch job in Docker:
42
+
43
+ ```bash
44
+ docker run --name mcc-mnc-list-fetch mcc-mnc-list
45
+ ```
46
+
47
+ Copy generated files to your host, then remove the container:
48
+
49
+ ```bash
50
+ docker cp mcc-mnc-list-fetch:/mnc-mcc-list/mcc-mnc-list.json ./mcc-mnc-list.json
51
+ docker cp mcc-mnc-list-fetch:/mnc-mcc-list/status-codes.json ./status-codes.json
52
+ docker rm mcc-mnc-list-fetch
53
+ ```
54
+
55
+ ## 🗂️ Data
56
+
57
+ ### `mcc-mnc-list.json`
58
+
59
+ This file contains all the records fetched from the Wikipedia page.
60
+
61
+ Structure of a single record:
62
+
63
+ ```js
64
+ {
65
+ "type": <String> - 'Test' / 'National' / 'International'
66
+ "countryName": <String> - country name
67
+ "countryCode": <String> - ISO 3166-1 country code
68
+ "mcc": <String> - mobile country code
69
+ "mnc": <String> - mobile network code
70
+ "brand": <String|null>
71
+ "operator": <String|null>
72
+ "status": <String> - status code ( see status-codes.json )
73
+ "bands": <String|null>
74
+ "notes": <String|null>
75
+ }
76
+ ```
77
+
78
+
79
+ ### `status-codes.json`
80
+
81
+ List ( `Array` ) of all the different Status Codes from MCC/MNC list.
82
+
83
+
84
+
85
+ ## 🚀 Usage
86
+
87
+ ## `.all()` : Array
88
+
89
+ Returns the full record list
90
+
91
+ ## `.statusCodes()` : Array
92
+
93
+ Returns the status code list
94
+
95
+ ## `.filter(filters)` : Array
96
+
97
+ Returns a filtered record list. `filters` is an object.
98
+
99
+ ```js
100
+ // get all the Operational mobile networks
101
+ let filters = { statusCode: 'Operational' }
102
+
103
+ // get all the records from Hungary
104
+ let filters = { mcc: '216' }
105
+
106
+ // get a specific network item ( specified with two keys )
107
+ let filters = { mcc: '216', mnc: '30' }
108
+
109
+ // get a specific network item ( specified with a joined key )
110
+ let filters = { mccmnc: '21630' }
111
+
112
+ // get all the Operational mobile networks from Hungary
113
+ let filters = { statusCode: 'Operational', mcc: '216' }
114
+
115
+ // get all the Operational mobile networks from US countryCode
116
+ let filters = { statusCode: 'Operational', countryCode: 'US' }
117
+
118
+ // get all the records
119
+ let filters = {}
120
+ ```
121
+
122
+ ## `.find(filters)` : Record | undefined
123
+
124
+ Returns the value of the first record in the array that satisfies the provided filters. Otherwise ``undefined`` is returned.
125
+ Filters are identical to the filters described in ``.filter(filters)``.
126
+
127
+
128
+ ## 🧪 Example
129
+
130
+ ```js
131
+
132
+ const mcc_mnc_list = require('@cavoq/mcc-mnc-list');
133
+
134
+ let records = mcc_mnc_list.all();
135
+ let statusCodes = mcc_mnc_list.statusCodes();
136
+
137
+ console.log(records.length);
138
+ // 2189
139
+
140
+ console.log(statusCodes.length);
141
+ // 12
142
+
143
+ console.log(mcc_mnc_list.filter({ mccmnc: '21630' }));
144
+ // [{
145
+ // "type": "National",
146
+ // "countryName": "Hungary",
147
+ // "countryCode": "HU",
148
+ // "mcc": "216",
149
+ // "mnc": "30",
150
+ // "brand": "T-Mobile",
151
+ // "operator": "Magyar Telekom Plc",
152
+ // "status": "Operational",
153
+ // "bands": "GSM 900 / GSM 1800 / UMTS 2100 / LTE 800 / LTE 1800 / LTE 2600",
154
+ // "notes": "Former WESTEL, Westel 900; MNC has the same numerical value as the area code"
155
+ // }]
156
+ ```
157
+
158
+ ## 📄 License
159
+
160
+ **mcc-mnc-list** is licensed under the MIT Open Source license. For more information, see the LICENSE file in this repository.
@@ -0,0 +1,17 @@
1
+ const js = require("@eslint/js");
2
+ const globals = require("globals");
3
+
4
+ module.exports = [
5
+ js.configs.recommended,
6
+ {
7
+ files: ["**/*.js"],
8
+ languageOptions: {
9
+ ecmaVersion: 8,
10
+ sourceType: "commonjs",
11
+ globals: Object.assign({}, globals.node),
12
+ },
13
+ rules: {
14
+ "no-console": "off",
15
+ },
16
+ },
17
+ ];
package/fetch.js ADDED
@@ -0,0 +1,249 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const jsdom = require("jsdom");
6
+ const { JSDOM } = jsdom;
7
+
8
+ const WIKI_URL = "https://en.wikipedia.org/wiki/Mobile_country_code";
9
+ const WIKI_URL_REGIONS = [
10
+ "https://en.wikipedia.org/wiki/Mobile_Network_Codes_in_ITU_region_2xx_(Europe)",
11
+ "https://en.wikipedia.org/wiki/Mobile_Network_Codes_in_ITU_region_3xx_(North_America)",
12
+ "https://en.wikipedia.org/wiki/Mobile_Network_Codes_in_ITU_region_4xx_(Asia)",
13
+ "https://en.wikipedia.org/wiki/Mobile_Network_Codes_in_ITU_region_5xx_(Oceania)",
14
+ "https://en.wikipedia.org/wiki/Mobile_Network_Codes_in_ITU_region_6xx_(Africa)",
15
+ "https://en.wikipedia.org/wiki/Mobile_Network_Codes_in_ITU_region_7xx_(South_America)",
16
+ ];
17
+
18
+ const MCC_MNC_OUTPUT_FILE = path.join(__dirname, "mcc-mnc-list.json");
19
+ const STATUS_CODES_OUTPUT_FILE = path.join(__dirname, "status-codes.json");
20
+
21
+ function fetch() {
22
+ let records = [];
23
+ let statusCodes = [];
24
+
25
+ const process = (region, records, statusCodes) =>
26
+ new Promise((resolve) => collect(resolve, region, records, statusCodes));
27
+
28
+ (async function loop() {
29
+ for (let i = 0; i < WIKI_URL_REGIONS.length; i++) {
30
+ const region = WIKI_URL_REGIONS[i];
31
+ await process(region, records, statusCodes);
32
+ console.log(region, records.length, statusCodes.length);
33
+ }
34
+
35
+ await process(WIKI_URL, records, statusCodes, true);
36
+ console.log(WIKI_URL, records.length, statusCodes.length);
37
+
38
+ writeData(records, statusCodes);
39
+ })();
40
+ }
41
+
42
+ function collect(resolve, from, records, statusCodes, globals) {
43
+ JSDOM.fromURL(from).then((dom) => {
44
+ const { window } = dom;
45
+ var content = window.document.querySelector(
46
+ "#mw-content-text > .mw-parser-output"
47
+ );
48
+
49
+ if (!content.hasChildNodes()) {
50
+ console.log("ERROR - empty content");
51
+ return;
52
+ }
53
+
54
+ content = removeCiteReferences(content);
55
+
56
+ const children = content.childNodes;
57
+ let recordType,
58
+ countryInfo = { name: null, code: null };
59
+
60
+ for (let i = 0; i < children.length; i++) {
61
+ let node = children[i];
62
+
63
+ if (!node.textContent.trim()) {
64
+ continue;
65
+ }
66
+
67
+ if (node.nodeName === "DIV") {
68
+ switch (node.classList.value) {
69
+ case "mw-heading mw-heading2":
70
+ recordType = getRecordType(node);
71
+ break;
72
+ case "mw-heading mw-heading4":
73
+ countryInfo = getCountryInfo(node);
74
+ break;
75
+ }
76
+ }
77
+
78
+ if (node.nodeName === "TABLE") {
79
+ if (globals && recordType === "National") {
80
+ continue;
81
+ }
82
+
83
+ let rows = node.querySelectorAll("tr");
84
+
85
+ for (let j = 1; j < rows.length; j++) {
86
+ let cols = rows[j].querySelectorAll("td");
87
+
88
+ if (cols.length < 7) {
89
+ // console.log('WARN invalid table row:', rows[j], node.textContent);
90
+ continue;
91
+ }
92
+
93
+ /*
94
+ Remove hidden child node of first cell:
95
+
96
+ <td><div style="overflow:hidden;width:0;height:0;margin:-1ex;float:right">
97
+ <h3><span class="mw-headline" id="United_States_of_America_-_US_-_313">United States of America - US - 313</span></h3>
98
+ </div>313
99
+ </td>
100
+ */
101
+ const mccChild = cols[0].querySelector("div");
102
+ if (mccChild !== null) {
103
+ cols[0].removeChild(mccChild);
104
+ }
105
+
106
+ let status = cleanup(cols[4].textContent);
107
+ if (status === "Not Operational" || status === "Not opearational") {
108
+ status = "Not operational";
109
+ }
110
+ if (status === "operational") {
111
+ status = "Operational";
112
+ }
113
+
114
+ if (status && statusCodes.indexOf(status) === -1) {
115
+ statusCodes.push(status);
116
+ }
117
+
118
+ records.push({
119
+ type: recordType,
120
+ countryName: countryInfo.name,
121
+ countryCode: countryInfo.code,
122
+ mcc: cleanup(cols[0].textContent),
123
+ mnc: cleanup(cols[1].textContent),
124
+ brand: cleanup(cols[2].textContent),
125
+ operator: cleanup(cols[3].textContent),
126
+ status: status,
127
+ bands: cleanup(cols[5].textContent),
128
+ notes: cleanup(cols[6].textContent),
129
+ });
130
+ }
131
+ }
132
+ }
133
+
134
+ resolve();
135
+ });
136
+ }
137
+
138
+ function writeData(records, statusCodes) {
139
+ fs.writeFile(
140
+ MCC_MNC_OUTPUT_FILE,
141
+ JSON.stringify(records, null, 2),
142
+ (err) => {
143
+ if (err) {
144
+ throw err;
145
+ }
146
+ console.log("MCC-MNC list saved to " + MCC_MNC_OUTPUT_FILE);
147
+ console.log("Total " + records.length + " records");
148
+ }
149
+ );
150
+
151
+ statusCodes.sort();
152
+
153
+ fs.writeFile(
154
+ STATUS_CODES_OUTPUT_FILE,
155
+ JSON.stringify(statusCodes, null, 2),
156
+ (err) => {
157
+ if (err) {
158
+ throw err;
159
+ }
160
+ console.log("Status codes saved to " + STATUS_CODES_OUTPUT_FILE);
161
+ }
162
+ );
163
+ }
164
+
165
+ function getRecordType(node) {
166
+ const recordTypeMap = {
167
+ "National operators": "National",
168
+ "Test networks": "Test",
169
+ "International operators": "International",
170
+ };
171
+
172
+ const h2Node = Array.from(node.childNodes).find(
173
+ (child) => child.nodeName === "H2"
174
+ );
175
+
176
+ if (h2Node) {
177
+ const sectionName = h2Node.textContent.trim();
178
+
179
+ if (sectionName.length > 1) {
180
+ if (
181
+ sectionName === "See also" ||
182
+ sectionName === "External links" ||
183
+ sectionName === "National MNC Authorities"
184
+ ) {
185
+ return null;
186
+ }
187
+
188
+ return recordTypeMap[sectionName] || "other";
189
+ }
190
+ }
191
+
192
+ return "other";
193
+ }
194
+
195
+ function getCountryInfo(node) {
196
+ const h4Node = Array.from(node.childNodes).find(
197
+ (child) => child.nodeName === "H4"
198
+ );
199
+
200
+ let countryName = null;
201
+ let countryCode = null;
202
+
203
+ if (h4Node) {
204
+ const countryText = h4Node.textContent.trim();
205
+ const dashPos = countryText.indexOf("–");
206
+
207
+ if (dashPos !== -1) {
208
+ countryName = countryText.substring(0, dashPos).trim();
209
+ countryCode = countryText.substring(dashPos + 1).trim();
210
+ }
211
+ }
212
+
213
+ return { name: countryName, code: countryCode };
214
+ }
215
+
216
+ function removeCiteReferences(nodes) {
217
+ let links = nodes.querySelectorAll("a");
218
+ for (let i = 0; i < links.length; i++) {
219
+ let link = links[i];
220
+ let href = link.getAttribute("href");
221
+ if (href.substr(0, 10) === "#cite_note") {
222
+ link.remove();
223
+ }
224
+ }
225
+
226
+ return nodes;
227
+ }
228
+
229
+ function cleanup(str) {
230
+ str = str.trim();
231
+ str = removeCitationNeeded(str);
232
+ if (str.substr(0, 1) === "[" && str.substr(-1) === "]") {
233
+ // remove brackets-only like [7]
234
+ str = "";
235
+ }
236
+ if (str.substr(0, 1) != "[" && str.substr(-1) === "]") {
237
+ // remove postfix references like ...[7]
238
+ let index = str.lastIndexOf("[");
239
+ str = str.substr(0, index - 1).trim();
240
+ }
241
+ str = str.replace(/“/g, '"');
242
+ return str.length ? str : null;
243
+ }
244
+
245
+ function removeCitationNeeded(str) {
246
+ return str.replace(/\[citation needed\]/g, "");
247
+ }
248
+
249
+ fetch();
package/index.js ADDED
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+
3
+ const records = require("./mcc-mnc-list.json");
4
+ const statusCodeList = require("./status-codes.json");
5
+
6
+ function all() {
7
+ return records;
8
+ }
9
+
10
+ function statusCodes() {
11
+ return statusCodeList;
12
+ }
13
+
14
+ function filter(filters) {
15
+ if (filters === undefined || filters === null) {
16
+ return records;
17
+ }
18
+
19
+ if (typeof filters !== "object") {
20
+ throw new TypeError("Invalid parameter (object expected)");
21
+ }
22
+
23
+ let statusCode, mcc, mnc, countryCode;
24
+
25
+ if (filters.statusCode) {
26
+ statusCode = filters.statusCode;
27
+ if (statusCodeList.indexOf(statusCode) === -1) {
28
+ throw new TypeError(
29
+ "Invalid statusCode parameter (not found in statusCode list)"
30
+ );
31
+ }
32
+ }
33
+
34
+ if (filters.mccmnc) {
35
+ let mccmnc;
36
+ if (
37
+ typeof filters.mccmnc === "string" ||
38
+ typeof filters.mccmnc === "number"
39
+ ) {
40
+ mccmnc = String(filters.mccmnc);
41
+ } else {
42
+ throw new TypeError("Invalid mccmnc parameter (string expected)");
43
+ }
44
+ mcc = mccmnc.substr(0, 3);
45
+ mnc = mccmnc.substr(3);
46
+ }
47
+
48
+ if (filters.mcc && mcc) {
49
+ throw new TypeError("Don't use mccmnc and mcc parameter at once");
50
+ }
51
+ if (filters.mnc && mnc) {
52
+ throw new TypeError("Don't use mccmnc and mnc parameter at once");
53
+ }
54
+
55
+ if (filters.mcc) {
56
+ if (typeof filters.mcc === "string" || typeof filters.mcc === "number") {
57
+ mcc = String(filters.mcc);
58
+ } else {
59
+ throw new TypeError("Invalid mcc parameter (string expected)");
60
+ }
61
+ }
62
+
63
+ if (filters.mnc) {
64
+ if (typeof filters.mnc === "string" || typeof filters.mnc === "number") {
65
+ mnc = String(filters.mnc);
66
+ } else {
67
+ throw new TypeError("Invalid mnc parameter (string expected)");
68
+ }
69
+ }
70
+
71
+ if (filters.countryCode != undefined) {
72
+ if (typeof filters.countryCode === "string") {
73
+ countryCode = filters.countryCode;
74
+ } else {
75
+ throw new TypeError("Invalid countryCode parameter (string expected)");
76
+ }
77
+ }
78
+
79
+ let result = records;
80
+
81
+ if (statusCode) {
82
+ result = result.filter((record) => record["status"] === statusCode);
83
+ }
84
+ if (countryCode) {
85
+ result = result.filter((record) => record["countryCode"] === countryCode);
86
+ }
87
+ if (mcc) {
88
+ result = result.filter((record) => record["mcc"] === mcc);
89
+ }
90
+ if (mnc) {
91
+ result = result.filter((record) => record["mnc"] === mnc);
92
+ }
93
+
94
+ return result;
95
+ }
96
+
97
+ function find(filters) {
98
+ // return the first element of undefined, as filter will always return an array
99
+ return filter(filters)[0];
100
+ }
101
+
102
+ module.exports = { all, statusCodes, filter, find };