1 import { pathExists, writeFile } from 'fs-extra'
2 import maxmind, { CountryResponse, Reader } from 'maxmind'
3 import { join } from 'path'
4 import { CONFIG } from '@server/initializers/config'
5 import { logger, loggerTagsFactory } from './logger'
6 import { isBinaryResponse, peertubeGot } from './requests'
8 const lTags = loggerTagsFactory('geo-ip')
10 const mmbdFilename = 'dbip-country-lite-latest.mmdb'
11 const mmdbPath = join(CONFIG.STORAGE.BIN_DIR, mmbdFilename)
14 private static instance: GeoIP
16 private reader: Reader<CountryResponse>
18 private constructor () {
21 async safeCountryISOLookup (ip: string): Promise<string> {
22 if (CONFIG.GEO_IP.ENABLED === false) return null
24 await this.initReaderIfNeeded()
27 const result = this.reader.get(ip)
28 if (!result) return null
30 return result.country.iso_code
32 logger.error('Cannot get country from IP.', { err })
38 async updateDatabase () {
39 if (CONFIG.GEO_IP.ENABLED === false) return
41 const url = CONFIG.GEO_IP.COUNTRY.DATABASE_URL
43 logger.info('Updating GeoIP database from %s.', url, lTags())
45 const gotOptions = { context: { bodyKBLimit: 200_000 }, responseType: 'buffer' as 'buffer' }
48 const gotResult = await peertubeGot(url, gotOptions)
50 if (!isBinaryResponse(gotResult)) {
51 throw new Error('Not a binary response')
54 await writeFile(mmdbPath, gotResult.body)
57 this.reader = undefined
59 logger.info('GeoIP database updated %s.', mmdbPath, lTags())
61 logger.error('Cannot update GeoIP database from %s.', url, { err, ...lTags() })
65 private async initReaderIfNeeded () {
67 if (!await pathExists(mmdbPath)) {
68 await this.updateDatabase()
71 this.reader = await maxmind.open(mmdbPath)
75 static get Instance () {
76 return this.instance || (this.instance = new this())