Skip to content

Commit 0533dbf

Browse files
committed
feat(sync) Detect devices for exist brands (matomo-org#7939)
feat(device) detect brand Samsung: Galaxy W24 Fold (SM-W9024), Galaxy Xcover 6 Pro (SM-G736U), Galaxy S23 FE (SM-S711N), Galaxy Tab S9 11" WiFi (SM-X710), Galaxy Tab S10+ 12.4" WiFi (SM-X820), Galaxy Tab S10+ 12.4" 5G (SM-X826B) feat(device) detect brand Xiaomi: Mi 8 SE, Mi 8 Youth, Mi 8 Pro, Mi 8 feat(device) detect brand Samsung: Galaxy Z Fold 6 (SM-F956N) feat(device) detect brand CUBOT: Max 5 feat(device) detect brand iTel: P65 (P671L) feat(device) detect brand Lenovo: Z6 Pro (L78051) feat(device) detect brand Cat: S52 feat(device) detect brand Lava: Z3 (LZG403 25I) feat(device) detect brand Huawei: Mate 10 (ALP-L09S) feat(device) detect brand Teclast: P25T feat(device) detect brand Vekta: LD-32SF4850BS feat(device) detect brand TopDevice: TDTV24BS01H feat(device) detect brand Tuvio: TD43FFBHV1, TD43UFBHV1, TD50UFBHV1, TD50UFGHV1, TD65UFBSV1 feat(device) detect brand Lenovo: Tab M10 HD (TB-X306X, TB-X306F) feat(device) detect brand Starlight: Gionee Star feat(device) detect brand Neffos: X20 feat(device) detect brand Minix: MNBP-300G feat(device) detect brand Prestigio: PTV50SS08X feat(device) detect brand HIPER: H24YQ2200GR feat(device) detect brand Novex: NVX-75U131MSY, NVX-32H219MSY feat(device) detect brand CENTEK: CT-8750 feat(device) detect brand Hiberg: QLED 65Y (HIBERGQLED65Y), 65Y UHD (HIBERG65YUHD) feat(device) detect brand Huawei: P smart (FIG-L31), Y6 (2019), Y6 Prime (2018), Y6 Pro (2019) feat(device) detect brand ZTE: Blade A7 (2020), Blade A5 (2020), Blade A5 (2019), Blade A3 (2020), Blade A3 (2019), Blade L8, Blade V10, Blade A3 Lite, Blade 20 Smart feat(device) detect brand Lenovo: Z5s (L78071), Z5 (L78011), S5 Pro GT (L58091), K10 Plus (L39051), A7 (L19111), A6 Note (L19041) feat(device) detect brand Huawei: Pura 70 (ADY-AL10) feat(device) detect brand Motorola: Moto G100, Moto G Pure (XT2163-4), Moto G Fast (XT2045-3) feat(device) detect brand Xiaomi: Mi Max 2 (MDE40) feat(device) detect brand ZTE: ZMax 5G (Z7540) feat(device) detect brand TCL: 403 (T431P) feat(device) detect brand POCO: C51 (2302EPCC4H) feat(device) detect brand Nubia: Focus 5G (NX302J) --- feat(client) detect mobile apps: Klara, Edmodo (matomo-org#7940) --- feat(sync) Adds detection for Meta Horizon (matomo-org#7928) --- feat(sync) Improve Android regexes (matomo-org#7945) * Improve Android regexes and add tests --- feat(sync) Improves version detection for Android (matomo-org#7946) --- feat(sync) Adds detection for Smart Tech brand and RTOS & Next os, and improves detection for Hisense, MediaTek, Next & NextStar (matomo-org#7941) * Improves detection for Next & NextStar devices * Adds detection for RTOS & Next * Adds detection for Smart Tech brand * Improves detection for Hisense devices * Remove MediaTek since is not a brand --- feat(sync) Adds detection for All Star, Astro (UA), astro (MY), FUJICOM, GN Electronics, TUCSON, UNITED brands and improves detection for various brands (matomo-org#7948) * Adds detection for GN Electronics brand * Improves detection for Graetz * Adds detection for UNITED brand * Adds detection for FUJICOM brand * Adds detection for TUCSON brand * Adds detection for astro brand * Improves detection for LG devices * Improves detection for AWOX devices * Improves detection for Blaupunkt devices * Improves detection for Botech devices * Improves detection for BAUHN devices * Adds detection for All Star brand * Improves detection for AXEN devices * Improves detection for GVC Pro devices * Improves detection for PEAQ devices * Improves detection for Hyundai devices --- feat(sync) Detect new brands: MEGAMAX, PLDT and Detect devices for exist brands (matomo-org#7952) feat(device) detect brand DNS: S3504 Meteor 3.5" feat(device) detect brand QMobile: Y2 Tab feat(device) detect new brand PLDT: TelPad MT7 feat(device) detect brand Vivo: X200 (V2415A) feat(device) detect brand TCL: 10 5G UW feat(device) detect new brand MEGAMAX: 42MG11FSM feat(device) detect brand Oilsky: M303 Pro feat(device) detect brand Meizu: X8 feat(device) detect brand Vivo: X200 Pro Mini (V2419A) feat(device) detect brand Hotwav: Cyber 13 feat(device) detect brand OnePlus: 13 (PJZ110) feat(device) detect brand CUBOT: King Kong Star 2 feat(device) detect brand Maunfeld: MLT55USD02G, MLT43FSX02 feat(device) detect brand Vityaz: 32LH1212 feat(device) detect brand DEXP: U55F8000QG feat(device) detect brand Tuvio: TD55UFGHV1
1 parent e12bb9e commit 0533dbf

26 files changed

+2757
-169
lines changed

client-hints.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,13 @@ class ClientHints {
110110
*/
111111
#parseHints(hints, result) {
112112
for (let key in hints) {
113-
let value = hints[key];
113+
const value = hints[key];
114114
const lowerCaseKey = key.toLowerCase().replace('_', '-');
115115

116+
if (void 0 === value || null === value || '' === value) {
117+
continue;
118+
}
119+
116120
switch (lowerCaseKey) {
117121
case 'http-sec-ch-ua-arch':
118122
case 'sec-ch-ua-arch':

parser/device/brand-short.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ module.exports = {
5858
'6L': 'ALDI SÜD',
5959
'3L': 'Alfawise',
6060
'4A': 'Aligator',
61+
'ALS': 'All Star',
6162
'AA': 'AllCall',
6263
'3A': 'AllDocube',
6364
'A2': 'Allview',
@@ -108,7 +109,8 @@ module.exports = {
108109
'59': 'ArtLine',
109110
'8A': 'Asano',
110111
'90': 'Asanzo',
111-
'1U': 'Astro',
112+
'1U': 'Astro (UA)',
113+
'AST': 'astro (MY)',
112114
'A4': 'Ask',
113115
'A8': 'Assistant',
114116
'ASS': 'ASSE',
@@ -572,6 +574,7 @@ module.exports = {
572574
'1F': 'FMT',
573575
'FPT': 'FPT',
574576
'FG': 'Fuego',
577+
'FUJ': 'FUJICOM',
575578
'FU': 'Fujitsu',
576579
'4F': 'Funai',
577580
'5J': 'Fusion5',
@@ -618,6 +621,7 @@ module.exports = {
618621
'38': 'GLONYX',
619622
'U6': 'Glofiish',
620623
'GLO': 'Glory Star',
624+
'GNE': 'GN Electronics',
621625
'G7': 'GoGEN',
622626
'GC': 'GOCLEVER',
623627
'5G': 'Gocomma',
@@ -1027,12 +1031,12 @@ module.exports = {
10271031
'0M': 'Mecool',
10281032
'MEM': 'MeMobile',
10291033
'MC': 'Mediacom',
1030-
'MK': 'MediaTek',
10311034
'MD': 'Medion',
10321035
'M2': 'MEEG',
10331036
'MEG': 'MEGA VISION',
10341037
'MCA': 'Megacable',
10351038
'MP': 'MegaFon',
1039+
'MGX': 'MEGAMAX',
10361040
'X0': 'mPhone',
10371041
'3M': 'Meitu',
10381042
'M1': 'Meizu',
@@ -1276,6 +1280,7 @@ module.exports = {
12761280
'PND': 'Panodic',
12771281
'PA1': 'Panoramic',
12781282
'PLT': 'Platoon',
1283+
'PLD': 'PLDT',
12791284
'94': 'Packard Bell',
12801285
'H9': 'Parrot Mobile',
12811286
'PAR': 'Partner Mobile',
@@ -1511,6 +1516,7 @@ module.exports = {
15111516
'19': 'Smailo',
15121517
'SR': 'Smart Electronic',
15131518
'SMA': 'Smart Kassel',
1519+
'STE': 'Smart Tech',
15141520
'49': 'Smart',
15151521
'47': 'SmartBook',
15161522
'3B': 'Smartab',
@@ -1706,6 +1712,7 @@ module.exports = {
17061712
'UR': 'Turkcell',
17071713
'4U': 'TuCEL',
17081714
'TUV': 'Tuvio',
1715+
'TUC': 'TUCSON',
17091716
'2U': 'Türk Telekom',
17101717
'TV': 'TVC',
17111718
'TW': 'TWM',
@@ -1732,6 +1739,7 @@ module.exports = {
17321739
'UNI': 'Unistrong',
17331740
'U2': 'UNIWA',
17341741
'UND': 'Uniden',
1742+
'UNE': 'UNITED',
17351743
'UGR': 'United Group',
17361744
'UO': 'Unnecto',
17371745
'UNN': 'Unnion Technologies',

parser/os-abstract-parser.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,12 @@ class OsAbstractParser extends ParserAbstract {
277277
short = data.short_name;
278278
}
279279

280+
// Meta Horizon is reported as Linux in client hints
281+
if ('GNU/Linux' === name && 'Meta Horizon' === data.name) {
282+
name = data.name;
283+
short = data.short_name;
284+
}
285+
280286
} else if (data) {
281287
name = data.name;
282288
version = data.version;

parser/os/os_families.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module.exports = {
33
'Android': [
44
'AND', 'CYN', 'FIR', 'REM', 'RZD', 'MLD', 'MCD', 'YNS', 'GRI', 'HAR',
55
'ADR', 'CLR', 'BOS', 'REV', 'LEN', 'SIR', 'RRS', 'WER', 'PIC', 'ARM',
6-
'HEL', 'BYI', 'RIS', 'PUF', 'LEA',
6+
'HEL', 'BYI', 'RIS', 'PUF', 'LEA', 'MET',
77
],
88
'AmigaOS': ['AMG', 'MOR', 'ARO'],
99
'BlackBerry': ['BLB', 'QNX'],
@@ -30,7 +30,7 @@ module.exports = {
3030
'Mac': ['MAC'],
3131
'Mobile Gaming Console': ['PSP', 'NDS', 'XBX'],
3232
'OpenVMS': ['OVS'],
33-
'Real-time OS': ['MTK', 'TDX', 'MRE', 'JME', 'REX'],
33+
'Real-time OS': ['MTK', 'TDX', 'MRE', 'JME', 'REX', 'RXT'],
3434
'Other Mobile': ['WOS', 'POS', 'SBA', 'TIZ', 'SMG', 'MAE', 'LUN', 'GEO'],
3535
'Symbian': ['SYM', 'SYS', 'SY3', 'S60', 'S40'],
3636
'Unix': [

parser/os/os_systems.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ module.exports = {
8787
'MAG': 'Mageia',
8888
'MDR': 'Mandriva',
8989
'SMG': 'MeeGo',
90+
'MET': 'Meta Horizon',
9091
'MCD': 'MocorDroid',
9192
'MON': 'moonOS',
9293
'EZX': 'Motorola EZX',
@@ -136,6 +137,7 @@ module.exports = {
136137
'RRS': 'Resurrection Remix OS',
137138
'REX': 'REX',
138139
'RZD': 'RazoDroiD',
140+
'RXT': 'RTOS & Next',
139141
'SAB': 'Sabayon',
140142
'SSE': 'SUSE',
141143
'SAF': 'Sailfish OS',

regexes/client/mobile_apps.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2620,6 +2620,14 @@
26202620
name: 'TownNews Now'
26212621
version: '$1'
26222622

2623+
- regex: '^Klara/'
2624+
name: 'Klara'
2625+
version: ''
2626+
2627+
- regex: 'EdmodoAndroid/([\d.]+)'
2628+
name: 'Edmodo'
2629+
version: '$1'
2630+
26232631
# Electron generic apps
26242632
- regex: ' (?!(?:AppleWebKit|brave|Cypress|Franz|Mailspring|Notion|Basecamp|Evernote|catalyst|ramboxpro|BlueMail|BeakerBrowser|Dezor|TweakStyle|Colibri|Polypane|Singlebox|Skye|VibeMate|(?:d|LT|Glass|Sushi|Flash|OhHai)Browser|Sizzy))([a-z0-9]*)(?:-desktop|-electron-app)?/(\d+\.[\d.]+).*Electron/'
26252633
name: '$1'

regexes/device/mobiles.yml

Lines changed: 199 additions & 76 deletions
Large diffs are not rendered by default.

regexes/device/portable_media_player.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,13 @@ Surfans:
125125

126126
# Oilsky (oilsky.com.cn)
127127
Oilsky:
128-
regex: 'Oilsky (M501)(?:[);/ ]|$)'
128+
regex: 'Oilsky (M501|M303)(?:-Pro)?(?:[);/ ]|$)'
129129
device: 'portable media player'
130-
model: '$1'
130+
models:
131+
- regex: 'M303-Pro'
132+
model: 'M303 Pro'
133+
- regex: '(M501|M303)'
134+
model: '$1'
131135

132136
Diofox:
133137
regex: 'Diofox[ _](M8)(?:[);/ ]|$)'

regexes/device/televisions.yml

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -278,12 +278,36 @@ Fuego:
278278
- regex: '_TV_[A-Z0-9]+_([0-9]{4});'
279279
model: 'Smart TV ($1)'
280280

281+
# FUJICOM (https://fujicom.com/)
282+
FUJICOM:
283+
regex: 'FUJICOM[;,]'
284+
device: 'tv'
285+
models:
286+
- regex: '_TV_[A-Z0-9]+_([0-9]{4});'
287+
model: 'Smart TV ($1)'
288+
289+
# GN Electronics (https://www.gnelectronics.in/)
290+
GN Electronics:
291+
regex: 'GN_ELECTRONICS[;,]'
292+
device: 'tv'
293+
models:
294+
- regex: '_TV_[A-Z0-9]+_([0-9]{4});'
295+
model: 'Smart TV ($1)'
296+
281297
# GoGEN (https://www.gogen.cz/)
282298
GoGEN:
283299
regex: 'GOGEN[;,]'
284300
device: 'tv'
285301
model: ''
286302

303+
# Graetz (https://www.graetzitalia.it/)
304+
Graetz:
305+
regex: 'GRAETZ[;,]'
306+
device: 'tv'
307+
models:
308+
- regex: '_TV_[A-Z0-9]+_([0-9]{4});'
309+
model: 'Smart TV ($1)'
310+
287311
# Grundig
288312
Grundig:
289313
regex: '(OWB|(?:Amazon.+)?Grundig)'
@@ -656,14 +680,6 @@ Manhattan:
656680
- regex: 'T3'
657681
model: 'T3'
658682

659-
# MediaTek
660-
MediaTek:
661-
regex: 'MTK|MediaTek;'
662-
device: 'tv'
663-
models:
664-
- regex: '(MT[0-9]{4})'
665-
model: '$1'
666-
667683
# Medion
668684
Medion:
669685
regex: 'Medion'
@@ -1090,6 +1106,14 @@ TOKYO:
10901106
- regex: 'TOKTCLED32S'
10911107
model: 'TOKTCLED32S'
10921108

1109+
# TUCSON
1110+
TUCSON:
1111+
regex: 'TUCSON[;,]'
1112+
device: 'tv'
1113+
models:
1114+
- regex: '_TV_[A-Z0-9]+_([0-9]{4});'
1115+
model: 'Smart TV ($1)'
1116+
10931117
# Turbo-X
10941118
Turbo-X:
10951119
regex: 'TURBO-X[;,]'
@@ -1118,6 +1142,14 @@ Tsinghua Tongfang:
11181142
- regex: 'LE40GY15'
11191143
model: 'LE40GY15'
11201144

1145+
# UNITED (http://www.united-tv.eu/en/)
1146+
UNITED:
1147+
regex: 'UNITED[;,]'
1148+
device: 'tv'
1149+
models:
1150+
- regex: '_TV_[A-Z0-9]+_([0-9]{4});'
1151+
model: 'Smart TV ($1)'
1152+
11211153
# VANGUARD
11221154
VANGUARD:
11231155
regex: 'VANGUARD[;,]'
@@ -1220,8 +1252,9 @@ Xiaomi:
12201252

12211253
# Unknown
12221254
Unknown:
1223-
regex: 'OEM, ([a-z0-9_ -]+), (?:wired|wireless)|_TV_[A-Z0-9]+_([0-9]{4});'
1224-
device: 'tv'
1255+
regex: 'OEM, ([a-z0-9_ -]+), (?:wired|wireless)|_TV_[A-Z0-9]+_([0-9]{4});|LaTivu_(?:\d+[.\d]+)_([0-9]{4})'
12251256
models:
12261257
- regex: '_TV_[A-Z0-9]+_([0-9]{4});'
12271258
model: 'Smart TV ($1)'
1259+
- regex: 'LaTivu_(?:\d+[.\d]+)_([0-9]{4})'
1260+
model: 'Smart TV ($1)'

regexes/oss.yml

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@
55
# @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later
66
###############
77

8+
##########
9+
# RTOS & Next
10+
##########
11+
- regex: '^NEXT '
12+
name: 'RTOS & Next'
13+
version: ''
14+
15+
##########
16+
# Meta Horizon (https://developers.meta.com/horizon)
17+
##########
18+
- regex: '(?:Pacific|(?<!like )Quest).+OculusBrowser|Standalone HMD|PortalGo'
19+
name: 'Meta Horizon'
20+
version: ''
21+
822
##########
923
# LeafOS (https://www.leafos.org/)
1024
##########
@@ -733,52 +747,48 @@
733747
- regex: '(?:Android API \d+|\d+/tclwebkit(?:\d+[.\d]*)|(?:(?<!\d )Android/\d{2}|(?<!; |\d{4}/)Android (?!1[0-5])\d{2})(?![^ ]))'
734748
name: 'Android'
735749
versions:
736-
- regex: '(?:Android API 35|35/tclwebkit|Android[ /]35)'
750+
- regex: 'Android API 35|35/tclwebkit|Android[ /]35'
737751
version: '15'
738-
- regex: '(?:Android API 34|34/tclwebkit|Android[ /]34)'
752+
- regex: 'Android API 34|34/tclwebkit|Android[ /]34'
739753
version: '14'
740-
- regex: '(?:Android API 33|33/tclwebkit|Android[ /]33)'
754+
- regex: 'Android API 33|33/tclwebkit|Android[ /]33'
741755
version: '13'
742-
- regex: '(?:Android API 32|32/tclwebkit|Android[ /]32)'
756+
- regex: 'Android API 32|32/tclwebkit|Android[ /]32'
743757
version: '12.1'
744-
- regex: '(?:Android API 31|31/tclwebkit|Android[ /]31)'
758+
- regex: 'Android API 31|31/tclwebkit|Android[ /]31'
745759
version: '12'
746-
- regex: '(?:Android API 30|30/tclwebkit|Android[ /]30)'
760+
- regex: 'Android API 30|30/tclwebkit|Android[ /]30'
747761
version: '11'
748-
- regex: '(?:Android API 29|29/tclwebkit|Android[ /]29)'
762+
- regex: 'Android API 29|29/tclwebkit|Android[ /]29'
749763
version: '10'
750-
- regex: '(?:Android API 28|28/tclwebkit|Android[ /]28)'
764+
- regex: 'Android API 28|28/tclwebkit|Android[ /]28'
751765
version: '9'
752-
- regex: '(?:Android API 27|27/tclwebkit|Android[ /]27)'
766+
- regex: 'Android API 27|27/tclwebkit|Android[ /]27'
753767
version: '8.1'
754-
- regex: '(?:Android API 26|26/tclwebkit|Android[ /]26)'
768+
- regex: 'Android API 26|26/tclwebkit|Android[ /]26'
755769
version: '8'
756-
- regex: '(?:Android API 25|25/tclwebkit|Android[ /]25)'
770+
- regex: 'Android API 25|25/tclwebkit|Android[ /]25'
757771
version: '7.1'
758-
- regex: '(?:Android API 24|24/tclwebkit|Android[ /]24)'
772+
- regex: 'Android API 24|24/tclwebkit|Android[ /]24'
759773
version: '7'
760-
- regex: '(?:Android API 23|23/tclwebkit|Android[ /]23)'
774+
- regex: 'Android API 23|23/tclwebkit|Android[ /]23'
761775
version: '6'
762-
- regex: '(?:Android API 22|22/tclwebkit|Android[ /]22)'
776+
- regex: 'Android API 22|22/tclwebkit|Android[ /]22'
763777
version: '5.1'
764-
- regex: '(?:21/tclwebkit|Android[ /]21)'
778+
- regex: 'Android API 21|21/tclwebkit|Android[ /]21'
765779
version: '5'
766-
- regex: '(?:20|19)/tclwebkit'
767-
version: '4.4'
768-
- regex: 'Android[ /](?:20|19)'
780+
- regex: 'Android API (?:20|19)|(?:20|19)/tclwebkit|Android[ /](?:20|19)'
769781
version: '4.4'
770-
- regex: '(?:18/tclwebkit|Android[ /]18)'
782+
- regex: 'Android API 18|18/tclwebkit|Android[ /]18'
771783
version: '4.3'
772-
- regex: '(?:17/tclwebkit|Android[ /]17)'
784+
- regex: 'Android API 17|17/tclwebkit|Android[ /]17'
773785
version: '4.2'
774-
- regex: '(?:16/tclwebkit|Android[ /]16)'
775-
version: '4.2'
776-
- regex: '15/tclwebkit'
786+
- regex: 'Android API 16|16/tclwebkit|Android[ /]16'
787+
version: '4.1'
788+
- regex: 'Android API 15|15/tclwebkit|Android[ /]15'
777789
version: '4.0.3'
778-
- regex: '15/tclwebkit'
790+
- regex: 'Android API 14|14/tclwebkit|Android[ /]14'
779791
version: '4.0.1'
780-
- regex: 'Android[ /]15'
781-
version: '4'
782792

783793
##########
784794
# Android
@@ -835,7 +845,7 @@
835845
name: 'Android'
836846
version: ''
837847

838-
- regex: 'BeyondPod|AntennaPod|Podkicker|DoggCatcher|okhttp|Podcatcher Deluxe|Sonos/.+\(ACR_|Linux x86_64; Quest|.*WhatsApp/.*A$'
848+
- regex: 'BeyondPod|AntennaPod|Podkicker|DoggCatcher|okhttp|Podcatcher Deluxe|Sonos/.+\(ACR_|.*WhatsApp/.*A$'
839849
name: 'Android'
840850
version: ''
841851

tests/fixtures/clients/mobile_app.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2303,3 +2303,15 @@
23032303
type: mobile app
23042304
name: TownNews Now
23052305
version: "4.0"
2306+
-
2307+
user_agent: Klara/1 CFNetwork/1128.0.1 Darwin/19.6.0
2308+
client:
2309+
type: mobile app
2310+
name: Klara
2311+
version: ""
2312+
-
2313+
user_agent: Dalvik/2.1.0 (Linux; U; Android 11; SM-A725M Build/RP1A.200720.012) EdmodoAndroid/10.43.5(69a222)
2314+
client:
2315+
type: mobile app
2316+
name: Edmodo
2317+
version: 10.43.5

0 commit comments

Comments
 (0)