├── .github └── workflows │ └── cd_nuget.yml ├── .gitignore ├── IPinfo.Tests ├── IPApiTest.cs ├── IPinfo.Tests.csproj └── IPinfoTest.cs ├── IPinfo.sln ├── LICENSE ├── README.md ├── samples ├── CacheConfigurationSample │ ├── CacheConfigurationSample.csproj │ └── Program.cs ├── HttpClientConfigurationSample │ ├── HttpClientConfigurationSample.csproj │ └── Program.cs └── SynchronousCallSample │ ├── Program.cs │ └── SynchronousCallSample.csproj └── src └── IPinfo ├── Apis ├── BaseApi.cs └── IPApi.cs ├── Cache ├── CacheConfigurations.cs ├── CacheHandler.cs ├── CacheWrapper.cs └── ICache.cs ├── Exceptions ├── BaseApiException.cs ├── RequestFailedGeneralException.cs └── RequestQuotaExceededException.cs ├── Http ├── Client │ ├── HttpClientConfiguration.cs │ ├── HttpClientWrapper.cs │ ├── HttpContext.cs │ ├── IHttpClient.cs │ └── IHttpClientConfiguration.cs ├── Request │ └── HttpRequest.cs └── Response │ ├── HttpResponse.cs │ └── HttpStringResponse.cs ├── IPinfo.csproj ├── IPinfo.nuspec ├── IPinfoClient.cs ├── Models ├── ASN.cs ├── Abuse.cs ├── Carrier.cs ├── Company.cs ├── Continent.cs ├── CountryCurrency.cs ├── CountryFlag.cs ├── DomainsList.cs ├── IPResponse.cs └── Privacy.cs ├── Utilities ├── ApiHelper.cs ├── BogonHelper.cs ├── CountryHelper.cs └── JsonHelper.cs └── icon.png /.github/workflows/cd_nuget.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NuGet 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Setup .NET 16 | uses: actions/setup-dotnet@v3 17 | with: 18 | dotnet-version: '6.x' 19 | 20 | - name: Create Strong Name Keypair 21 | run: echo "${{ secrets.SNK_BASE64 }}" | base64 --decode > sgKeyIPinfoStrongName.snk 22 | 23 | - name: Restore dependencies 24 | run: dotnet restore 25 | 26 | - name: Test 27 | run: dotnet test 28 | env: 29 | IPINFO_TOKEN: ${{ secrets.IPINFO_TOKEN }} 30 | 31 | - name: Build 32 | run: dotnet build --configuration Release /p:AssemblyOriginatorKeyFile=sgKeyIPinfoStrongName.snk 33 | 34 | - name: Pack 35 | run: dotnet pack --configuration Release --output nupkgs /p:AssemblyOriginatorKeyFile=sgKeyIPinfoStrongName.snk 36 | 37 | - name: Publish to NuGet 38 | run: dotnet nuget push nupkgs/IPinfo.*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Referenced from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | nupkgs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.tlog 95 | *.vspscc 96 | *.vssscc 97 | .builds 98 | *.pidb 99 | *.svclog 100 | *.scc 101 | 102 | # Chutzpah Test files 103 | _Chutzpah* 104 | 105 | # Visual C++ cache files 106 | ipch/ 107 | *.aps 108 | *.ncb 109 | *.opendb 110 | *.opensdf 111 | *.sdf 112 | *.cachefile 113 | *.VC.db 114 | *.VC.VC.opendb 115 | 116 | # Visual Studio profiler 117 | *.psess 118 | *.vsp 119 | *.vspx 120 | *.sap 121 | 122 | # Visual Studio Trace Files 123 | *.e2e 124 | 125 | # TFS 2012 Local Workspace 126 | $tf/ 127 | 128 | # Guidance Automation Toolkit 129 | *.gpState 130 | 131 | # ReSharper is a .NET coding add-in 132 | _ReSharper*/ 133 | *.[Rr]e[Ss]harper 134 | *.DotSettings.user 135 | 136 | # TeamCity is a build add-in 137 | _TeamCity* 138 | 139 | # DotCover is a Code Coverage Tool 140 | *.dotCover 141 | 142 | # AxoCover is a Code Coverage Tool 143 | .axoCover/* 144 | !.axoCover/settings.json 145 | 146 | # Coverlet is a free, cross platform Code Coverage Tool 147 | coverage*.json 148 | coverage*.xml 149 | coverage*.info 150 | 151 | # Visual Studio code coverage results 152 | *.coverage 153 | *.coveragexml 154 | 155 | # NCrunch 156 | _NCrunch_* 157 | .*crunch*.local.xml 158 | nCrunchTemp_* 159 | 160 | # MightyMoose 161 | *.mm.* 162 | AutoTest.Net/ 163 | 164 | # Web workbench (sass) 165 | .sass-cache/ 166 | 167 | # Installshield output folder 168 | [Ee]xpress/ 169 | 170 | # DocProject is a documentation generator add-in 171 | DocProject/buildhelp/ 172 | DocProject/Help/*.HxT 173 | DocProject/Help/*.HxC 174 | DocProject/Help/*.hhc 175 | DocProject/Help/*.hhk 176 | DocProject/Help/*.hhp 177 | DocProject/Help/Html2 178 | DocProject/Help/html 179 | 180 | # Click-Once directory 181 | publish/ 182 | 183 | # Publish Web Output 184 | *.[Pp]ublish.xml 185 | *.azurePubxml 186 | # Note: Comment the next line if you want to checkin your web deploy settings, 187 | # but database connection strings (with potential passwords) will be unencrypted 188 | *.pubxml 189 | *.publishproj 190 | 191 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 192 | # checkin your Azure Web App publish settings, but sensitive information contained 193 | # in these scripts will be unencrypted 194 | PublishScripts/ 195 | 196 | # NuGet Packages 197 | *.nupkg 198 | # NuGet Symbol Packages 199 | *.snupkg 200 | # The packages folder can be ignored because of Package Restore 201 | **/[Pp]ackages/* 202 | # except build/, which is used as an MSBuild target. 203 | !**/[Pp]ackages/build/ 204 | # Uncomment if necessary however generally it will be regenerated when needed 205 | #!**/[Pp]ackages/repositories.config 206 | # NuGet v3's project.json files produces more ignorable files 207 | *.nuget.props 208 | *.nuget.targets 209 | 210 | # Microsoft Azure Build Output 211 | csx/ 212 | *.build.csdef 213 | 214 | # Microsoft Azure Emulator 215 | ecf/ 216 | rcf/ 217 | 218 | # Windows Store app package directories and files 219 | AppPackages/ 220 | BundleArtifacts/ 221 | Package.StoreAssociation.xml 222 | _pkginfo.txt 223 | *.appx 224 | *.appxbundle 225 | *.appxupload 226 | 227 | # Visual Studio cache files 228 | # files ending in .cache can be ignored 229 | *.[Cc]ache 230 | # but keep track of directories ending in .cache 231 | !?*.[Cc]ache/ 232 | 233 | # Others 234 | ClientBin/ 235 | ~$* 236 | *~ 237 | *.dbmdl 238 | *.dbproj.schemaview 239 | *.jfm 240 | *.pfx 241 | *.publishsettings 242 | orleans.codegen.cs 243 | 244 | # Including strong name files can present a security risk 245 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 246 | *.snk 247 | 248 | # Since there are multiple workflows, uncomment next line to ignore bower_components 249 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 250 | #bower_components/ 251 | 252 | # RIA/Silverlight projects 253 | Generated_Code/ 254 | 255 | # Backup & report files from converting an old project file 256 | # to a newer Visual Studio version. Backup files are not needed, 257 | # because we have git ;-) 258 | _UpgradeReport_Files/ 259 | Backup*/ 260 | UpgradeLog*.XML 261 | UpgradeLog*.htm 262 | ServiceFabricBackup/ 263 | *.rptproj.bak 264 | 265 | # SQL Server files 266 | *.mdf 267 | *.ldf 268 | *.ndf 269 | 270 | # Business Intelligence projects 271 | *.rdl.data 272 | *.bim.layout 273 | *.bim_*.settings 274 | *.rptproj.rsuser 275 | *- [Bb]ackup.rdl 276 | *- [Bb]ackup ([0-9]).rdl 277 | *- [Bb]ackup ([0-9][0-9]).rdl 278 | 279 | # Microsoft Fakes 280 | FakesAssemblies/ 281 | 282 | # GhostDoc plugin setting file 283 | *.GhostDoc.xml 284 | 285 | # Node.js Tools for Visual Studio 286 | .ntvs_analysis.dat 287 | node_modules/ 288 | 289 | # Visual Studio 6 build log 290 | *.plg 291 | 292 | # Visual Studio 6 workspace options file 293 | *.opt 294 | 295 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 296 | *.vbw 297 | 298 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 299 | *.vbp 300 | 301 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 302 | *.dsw 303 | *.dsp 304 | 305 | # Visual Studio 6 technical files 306 | *.ncb 307 | *.aps 308 | 309 | # Visual Studio LightSwitch build output 310 | **/*.HTMLClient/GeneratedArtifacts 311 | **/*.DesktopClient/GeneratedArtifacts 312 | **/*.DesktopClient/ModelManifest.xml 313 | **/*.Server/GeneratedArtifacts 314 | **/*.Server/ModelManifest.xml 315 | _Pvt_Extensions 316 | 317 | # Paket dependency manager 318 | .paket/paket.exe 319 | paket-files/ 320 | 321 | # FAKE - F# Make 322 | .fake/ 323 | 324 | # CodeRush personal settings 325 | .cr/personal 326 | 327 | # Python Tools for Visual Studio (PTVS) 328 | __pycache__/ 329 | *.pyc 330 | 331 | # Cake - Uncomment if you are using it 332 | # tools/** 333 | # !tools/packages.config 334 | 335 | # Tabs Studio 336 | *.tss 337 | 338 | # Telerik's JustMock configuration file 339 | *.jmconfig 340 | 341 | # BizTalk build output 342 | *.btp.cs 343 | *.btm.cs 344 | *.odx.cs 345 | *.xsd.cs 346 | 347 | # OpenCover UI analysis results 348 | OpenCover/ 349 | 350 | # Azure Stream Analytics local run output 351 | ASALocalRun/ 352 | 353 | # MSBuild Binary and Structured Log 354 | *.binlog 355 | 356 | # NVidia Nsight GPU debugger configuration file 357 | *.nvuser 358 | 359 | # MFractors (Xamarin productivity tool) working folder 360 | .mfractor/ 361 | 362 | # Local History for Visual Studio 363 | .localhistory/ 364 | 365 | # Visual Studio History (VSHistory) files 366 | .vshistory/ 367 | 368 | # BeatPulse healthcheck temp database 369 | healthchecksdb 370 | 371 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 372 | MigrationBackup/ 373 | 374 | # Ionide (cross platform F# VS Code tools) working folder 375 | .ionide/ 376 | 377 | # Fody - auto-generated XML schema 378 | FodyWeavers.xsd 379 | 380 | # VS Code files for those working on multiple tools 381 | .vscode/* 382 | # Below VS Code files can be added if required 383 | #!.vscode/settings.json 384 | #!.vscode/tasks.json 385 | #!.vscode/launch.json 386 | #!.vscode/extensions.json 387 | *.code-workspace 388 | 389 | # Local History for Visual Studio Code 390 | .history/ 391 | 392 | # Windows Installer files from build outputs 393 | *.cab 394 | *.msi 395 | *.msix 396 | *.msm 397 | *.msp 398 | 399 | # JetBrains Rider 400 | *.sln.iml 401 | 402 | -------------------------------------------------------------------------------- /IPinfo.Tests/IPApiTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Xunit; 4 | 5 | using IPinfo.Models; 6 | 7 | namespace IPinfo.Tests 8 | { 9 | public class IPApiTest 10 | { 11 | [Fact] 12 | public void TestGetDetails() 13 | { 14 | string ip = "8.8.8.8"; 15 | IPinfoClient client = new IPinfoClient.Builder() 16 | .AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN")) 17 | .Build(); 18 | 19 | IPResponse actual = client.IPApi.GetDetails(ip); 20 | 21 | var expectations = new List>() 22 | { 23 | new("8.8.8.8", actual.IP), 24 | new("dns.google", actual.Hostname), 25 | new("Mountain View", actual.City), 26 | new("California", actual.Region), 27 | new("US", actual.Country), 28 | new("United States", actual.CountryName), 29 | new(false, actual.IsEU), 30 | new("🇺🇸", actual.CountryFlag.Emoji), 31 | new("U+1F1FA U+1F1F8", actual.CountryFlag.Unicode), 32 | new("https://cdn.ipinfo.io/static/images/countries-flags/US.svg", actual.CountryFlagURL), 33 | new("USD", actual.CountryCurrency.Code), 34 | new("$", actual.CountryCurrency.Symbol), 35 | new("NA", actual.Continent.Code), 36 | new("North America", actual.Continent.Name), 37 | new("America/Los_Angeles", actual.Timezone), 38 | new("", actual.Privacy.Service), 39 | new(5, actual.Domains.Domains.Count), 40 | }; 41 | Assert.All(expectations, pair => Assert.Equal(pair.Item1, pair.Item2)); 42 | Assert.False(actual.Privacy.Proxy); 43 | Assert.False(actual.Privacy.Vpn); 44 | Assert.False(actual.Privacy.Tor); 45 | Assert.False(actual.Privacy.Relay); 46 | Assert.True(actual.Privacy.Hosting); 47 | } 48 | 49 | [Fact] 50 | public void TestBogonIPV4() 51 | { 52 | string ip = "127.0.0.1"; 53 | IPinfoClient client = new IPinfoClient.Builder() 54 | .AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN")) 55 | .Build(); 56 | 57 | IPResponse actual = client.IPApi.GetDetails(ip); 58 | 59 | Assert.Equal("127.0.0.1", actual.IP); 60 | Assert.True(actual.Bogon); 61 | } 62 | 63 | [Fact] 64 | public void TestBogonIPV6() 65 | { 66 | string ip = "2001:0:c000:200::0:255:1"; 67 | IPinfoClient client = new IPinfoClient.Builder() 68 | .AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN")) 69 | .Build(); 70 | 71 | IPResponse actual = client.IPApi.GetDetails(ip); 72 | 73 | Assert.Equal("2001:0:c000:200::0:255:1", actual.IP); 74 | Assert.True(actual.Bogon); 75 | } 76 | 77 | [Fact] 78 | public void TestNonBogonIPV4() 79 | { 80 | string ip = "1.1.1.1"; 81 | IPinfoClient client = new IPinfoClient.Builder() 82 | .AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN")) 83 | .Build(); 84 | 85 | IPResponse actual = client.IPApi.GetDetails(ip); 86 | 87 | Assert.Equal("1.1.1.1", actual.IP); 88 | Assert.False(actual.Bogon); 89 | } 90 | 91 | [Fact] 92 | public void TestNonBogonIPV6() 93 | { 94 | string ip = "2a03:2880:f10a:83:face:b00c:0:25de"; 95 | IPinfoClient client = new IPinfoClient.Builder() 96 | .AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN")) 97 | .Build(); 98 | 99 | IPResponse actual = client.IPApi.GetDetails(ip); 100 | 101 | Assert.Equal("2a03:2880:f10a:83:face:b00c:0:25de", actual.IP); 102 | Assert.False(actual.Bogon); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /IPinfo.Tests/IPinfo.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | all 16 | 17 | 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | all 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | <_Parameter1>..\sgKeyIPinfoStrongName.snk 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /IPinfo.Tests/IPinfoTest.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | // needed to call GetCountry method in test 4 | using IPinfo.Utilities; 5 | 6 | namespace IPinfo.Tests 7 | { 8 | public class IPinfoTest 9 | { 10 | [Theory] 11 | [InlineData("PK", "Pakistan")] 12 | [InlineData("US", "United States")] 13 | public void TestGetCountry(string countryCode, string expected) 14 | { 15 | // Arrange 16 | // Act 17 | string actual = CountryHelper.GetCountry(countryCode); // access to internal methods is provided in sdk's csproj 18 | 19 | // Assert 20 | Assert.Equal(expected, actual); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /IPinfo.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30114.105 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02C43CF7-CCE4-4739-B646-C2E70FF815AA}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IPinfo", "src\IPinfo\IPinfo.csproj", "{EFDBF1B9-B34A-4395-A88D-2C99D3672962}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{AA649695-0087-41C9-9F86-34448F5B1714}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IPinfo.Tests", "IPinfo.Tests\IPinfo.Tests.csproj", "{4333C2DD-4DF6-489C-908F-14ED4917C354}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HttpClientConfigurationSample", "samples\HttpClientConfigurationSample\HttpClientConfigurationSample.csproj", "{0CD901A0-A4A7-41E7-B2F9-4948D6AC658F}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CacheConfigurationSample", "samples\CacheConfigurationSample\CacheConfigurationSample.csproj", "{0A8AA1A0-6866-49C8-9A1A-CF2A86DD964E}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SynchronousCallSample", "samples\SynchronousCallSample\SynchronousCallSample.csproj", "{11FD025E-6F21-4327-A6D6-33917D7D12C6}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 29 | {EFDBF1B9-B34A-4395-A88D-2C99D3672962}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {EFDBF1B9-B34A-4395-A88D-2C99D3672962}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {EFDBF1B9-B34A-4395-A88D-2C99D3672962}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {EFDBF1B9-B34A-4395-A88D-2C99D3672962}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {4333C2DD-4DF6-489C-908F-14ED4917C354}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {4333C2DD-4DF6-489C-908F-14ED4917C354}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {4333C2DD-4DF6-489C-908F-14ED4917C354}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {4333C2DD-4DF6-489C-908F-14ED4917C354}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {0CD901A0-A4A7-41E7-B2F9-4948D6AC658F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {0CD901A0-A4A7-41E7-B2F9-4948D6AC658F}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {0CD901A0-A4A7-41E7-B2F9-4948D6AC658F}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {0CD901A0-A4A7-41E7-B2F9-4948D6AC658F}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {0A8AA1A0-6866-49C8-9A1A-CF2A86DD964E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {0A8AA1A0-6866-49C8-9A1A-CF2A86DD964E}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {0A8AA1A0-6866-49C8-9A1A-CF2A86DD964E}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {0A8AA1A0-6866-49C8-9A1A-CF2A86DD964E}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {11FD025E-6F21-4327-A6D6-33917D7D12C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {11FD025E-6F21-4327-A6D6-33917D7D12C6}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {11FD025E-6F21-4327-A6D6-33917D7D12C6}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {11FD025E-6F21-4327-A6D6-33917D7D12C6}.Release|Any CPU.Build.0 = Release|Any CPU 49 | EndGlobalSection 50 | GlobalSection(NestedProjects) = preSolution 51 | {EFDBF1B9-B34A-4395-A88D-2C99D3672962} = {02C43CF7-CCE4-4739-B646-C2E70FF815AA} 52 | {0CD901A0-A4A7-41E7-B2F9-4948D6AC658F} = {AA649695-0087-41C9-9F86-34448F5B1714} 53 | {0A8AA1A0-6866-49C8-9A1A-CF2A86DD964E} = {AA649695-0087-41C9-9F86-34448F5B1714} 54 | {11FD025E-6F21-4327-A6D6-33917D7D12C6} = {AA649695-0087-41C9-9F86-34448F5B1714} 55 | EndGlobalSection 56 | EndGlobal 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IPinfo IPinfo C# .NET SDK 2 | 3 | [![License](http://img.shields.io/:license-apache-blue.svg)](LICENSE) 4 | [![NuGet](https://img.shields.io/nuget/dt/IPinfo.svg?style=flat-square&label=IPinfo)](https://www.nuget.org/packages/IPinfo/) 5 | 6 | This is the official C# .NET SDK for the [IPinfo.io](https://ipinfo.io) IP address API, allowing you to look up your own IP address, or get any of the following details for other IP addresses: 7 | 8 | - [IP geolocation](https://ipinfo.io/ip-geolocation-api) (city, region, country, postal code, latitude, and longitude) 9 | - [ASN details](https://ipinfo.io/asn-api) (ISP or network operator, associated domain name, and type, such as business, hosting, or company) 10 | - [Firmographics data](https://ipinfo.io/ip-company-api) (the name and domain of the business that uses the IP address) 11 | - [Carrier information](https://ipinfo.io/ip-carrier-api) (the name of the mobile carrier and MNC and MCC for that carrier if the IP is used exclusively for mobile traffic) 12 | 13 | ## Getting Started 14 | 15 | You'll need an IPinfo API access token, which you can get by signing up for a free account at [https://ipinfo.io/signup](https://ipinfo.io/signup). 16 | 17 | The free plan is limited to 50,000 requests per month, and doesn't include some of the data fields such as IP type and company data. To enable all the data fields and additional request volumes see [https://ipinfo.io/pricing](https://ipinfo.io/pricing) 18 | 19 | ⚠️ Note: This SDK does not currently support our newest free API https://ipinfo.io/lite. If you’d like to use IPinfo Lite, you can call the [endpoint directly](https://ipinfo.io/developers/lite-api) using your preferred HTTP client. Developers are also welcome to contribute support for Lite by submitting a pull request. 20 | 21 | ### Installation 22 | 23 | This package can be installed from NuGet. 24 | 25 | ##### Install using Package Manager 26 | 27 | ```bash 28 | Install-Package IPinfo 29 | ``` 30 | 31 | ##### Install using the dotnet CLI 32 | 33 | ```bash 34 | dotnet add package IPinfo 35 | ``` 36 | 37 | ##### Install with NuGet.exe 38 | 39 | ```bash 40 | nuget install IPinfo 41 | ``` 42 | 43 | ### Quick Start 44 | 45 | ```csharp 46 | // namespace 47 | using IPinfo; 48 | using IPinfo.Models; 49 | ``` 50 | 51 | ```csharp 52 | // initializing IPinfo client 53 | string token = "MY_TOKEN"; 54 | IPinfoClient client = new IPinfoClient.Builder() 55 | .AccessToken(token) 56 | .Build(); 57 | ``` 58 | 59 | ### Usage 60 | 61 | ```csharp 62 | // making API call 63 | string ip = "216.239.36.21"; 64 | IPResponse ipResponse = await client.IPApi.GetDetailsAsync(ip); 65 | ``` 66 | 67 | ```csharp 68 | // accessing location details from response 69 | Console.WriteLine($"IPResponse.IP: {ipResponse.IP}"); 70 | Console.WriteLine($"IPResponse.City: {ipResponse.City}"); 71 | Console.WriteLine($"IPResponse.Company.Name: {ipResponse.Company.Name}"); 72 | Console.WriteLine($"IPResponse.Country: {ipResponse.Country}"); 73 | Console.WriteLine($"IPResponse.CountryName: {ipResponse.CountryName}"); 74 | ``` 75 | 76 | ### Synchronous 77 | 78 | ```csharp 79 | // making synchronous API call 80 | string ip = "216.239.36.21"; 81 | IPResponse ipResponse = client.IPApi.GetDetails(ip); 82 | ``` 83 | 84 | ### Country Name Lookup 85 | 86 | `ipResponse.CountryName` will return the country name, whereas `ipResponse.Country` can be used to fetch the country code. 87 | 88 | Additionally `ipResponse.IsEU` will return `true` if the country is a member of the European Union (EU), `response.CountryFlag` 89 | will return the emoji and Unicode of the country's flag, `response.CountryCurrency` will return the code and symbol of the country's currency 90 | ,and `response.Continent` will return the code and name of the continent. `response.CountryFlagURL` will return a public link to the country's flag image as an SVG which can be used anywhere. 91 | 92 | ```csharp 93 | string ip = "1.1.1.1"; 94 | 95 | // making API call 96 | IPResponse ipResponse = await client.IPApi.GetDetailsAsync(ip); 97 | 98 | // country code, e.g. 'US' 99 | Console.WriteLine($"IPResponse.Country: {ipResponse.Country}"); 100 | 101 | // country name, e.g. 'United States' 102 | Console.WriteLine($"IPResponse.CountryName: {ipResponse.CountryName}"); 103 | 104 | // whether part of the EU, e.g. false 105 | Console.WriteLine($"IPResponse.isEU: {ipResponse.isEU}"); 106 | 107 | // country flag emoji, e.g. "US" -> "🇺🇸" 108 | Console.WriteLine($"IPResponse.CountryFlag.Emoji: {ipResponse.CountryFlag.Emoji}"); 109 | 110 | // country flag url, e.g. "US" -> "https://cdn.ipinfo.io/static/images/countries-flags/US.svg" 111 | Console.WriteLine($"IPResponse.CountryFlagurl: {ipResponse.CountryFlagURL}"); 112 | 113 | // country flag unicode, e.g. "US" -> "U+1F1FA U+1F1F8" 114 | Console.WriteLine($"IPResponse.CountryFlag.Unicode: {ipResponse.CountryFlag.Unicode}"); 115 | 116 | // currency code, e.g. "US" -> "USD" 117 | Console.WriteLine($"IPResponse.CountryCurrency.Code: {ipResponse.CountryCurrency.Code}"); 118 | 119 | // currency symbol, e.g. "US" -> "$" 120 | Console.WriteLine($"IPResponse.CountryCurrency.Symbol: {ipResponse.CountryCurrency.Symbol}"); 121 | 122 | // continent code, e.g. "US" -> "NA" 123 | Console.WriteLine($"IPResponse.Continent.Code: {ipResponse.Continent.Code}"); 124 | 125 | // continent name, e.g. "US" -> "North America" 126 | Console.WriteLine($"IPResponse.Continent.Name: {ipResponse.Continent.Name}"); 127 | ``` 128 | 129 | ### Caching 130 | 131 | In-memory caching of data is provided by default. Custom implementation of the cache can also be provided by implementing the `ICache` interface. 132 | 133 | #### Modifying cache options 134 | 135 | ```csharp 136 | // namespace 137 | using IPinfo; 138 | using IPinfo.Cache; 139 | ``` 140 | 141 | ```csharp 142 | long cacheEntryTimeToLiveInSeconds = 2*60*60*24; // 2 days 143 | int cacheSizeMbs = 2; 144 | IPinfoClient client = new IPinfoClient.Builder() 145 | .AccessToken(token) // pass your token string 146 | .Cache(new CacheWrapper(cacheConfig => cacheConfig 147 | .CacheMaxMbs(cacheSizeMbs) // pass cache size in mbs 148 | .CacheTtl(cacheEntryTimeToLiveInSeconds))) // pass time to live in seconds for cache entry 149 | .Build(); 150 | ``` 151 | 152 | ### Bogon Filtering 153 | 154 | The `Bogon` property of the `IPResponse` object can be used to check if an IP address is a bogon. 155 | 156 | ```csharp 157 | // namespace 158 | using IPinfo; 159 | using IPinfo.Models; 160 | ``` 161 | 162 | ```csharp 163 | string ip = "127.0.0.1"; 164 | IPResponse ipResponse = await client.IPApi.GetDetailsAsync(ip); 165 | if (ipResponse.Bogon) 166 | { 167 | Console.WriteLine($"{ipResponse.IP} is a bogon."); 168 | } 169 | else 170 | { 171 | // display ip details 172 | Console.WriteLine($"IPResponse.IP: {ipResponse.IP}"); 173 | Console.WriteLine($"IPResponse.City: {ipResponse.City}"); 174 | Console.WriteLine($"IPResponse.CountryName: {ipResponse.CountryName}"); 175 | } 176 | ``` 177 | 178 | ### Thread Safety 179 | 180 | This library is thread safe when using default components. 181 | 182 | If you decide to replace the cache implementation with your own, you must guarantee thread safety within that library in regard to cache manipulations. 183 | 184 | ### Samples 185 | 186 | [Sample codes](https://github.com/ipinfo/csharp/tree/main/samples) are also available. 187 | 188 | ## Other Libraries 189 | 190 | There are official [IPinfo client libraries](https://ipinfo.io/developers/libraries) available for many languages including PHP, Go, Java, Ruby, and many popular frameworks such as Django, Rails, and Laravel. There are also many third-party libraries and integrations available for our API. 191 | 192 | ## About IPinfo 193 | 194 | Founded in 2013, IPinfo prides itself on being the most reliable, accurate, and in-depth source of IP address data available anywhere. We process terabytes of data to produce our custom IP geolocation, company, carrier, VPN detection, hosted domains, and IP type data sets. Our API handles over 40 billion requests a month for 100,000 businesses and developers. 195 | 196 | [![image](https://avatars3.githubusercontent.com/u/15721521?s=128&u=7bb7dde5c4991335fb234e68a30971944abc6bf3&v=4)](https://ipinfo.io/) 197 | -------------------------------------------------------------------------------- /samples/CacheConfigurationSample/CacheConfigurationSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exe 9 | net6.0 10 | enable 11 | enable 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /samples/CacheConfigurationSample/Program.cs: -------------------------------------------------------------------------------- 1 | using IPinfo; 2 | using IPinfo.Models; 3 | using IPinfo.Cache; 4 | 5 | namespace ConsoleApp 6 | { 7 | class Program 8 | { 9 | static async Task Main(string[] args) 10 | { 11 | Console.WriteLine("\nSample for changing configuration for built-in cache"); 12 | 13 | // to use this sample, add your IPinfo Access Token to environment variable 14 | // named "IPINFO_TOKEN", or initialize your token string directly. 15 | string? token = Environment.GetEnvironmentVariable("IPINFO_TOKEN"); 16 | 17 | long cacheEntryTimeToLiveInSeconds = 2*60*60*24; // 2 days 18 | int cacheSizeMbs = 2; 19 | 20 | if(token is not null) 21 | { 22 | // initializing IPinfo client 23 | IPinfoClient client = new IPinfoClient.Builder() 24 | .AccessToken(token) // pass your token string 25 | .Cache(new CacheWrapper(cacheConfig => cacheConfig 26 | .CacheMaxMbs(cacheSizeMbs) // pass cache size in mbs 27 | .CacheTtl(cacheEntryTimeToLiveInSeconds))) // pass time to live in seconds for cache entry 28 | .Build(); 29 | 30 | string ip = PromptHelper(); 31 | while(!ip.Equals("0")) 32 | { 33 | // making API call 34 | IPResponse ipResponse = await client.IPApi.GetDetailsAsync(ip); 35 | 36 | Console.WriteLine($"IPResponse.IP: {ipResponse.IP}"); 37 | Console.WriteLine($"IPResponse.City: {ipResponse.City}"); 38 | Console.WriteLine($"IPResponse.Company.Name: {ipResponse.Company.Name}"); 39 | Console.WriteLine($"IPResponse.Country: {ipResponse.Country}"); 40 | Console.WriteLine($"IPResponse.CountryName: {ipResponse.CountryName}"); 41 | 42 | ip = PromptHelper(); 43 | } 44 | } 45 | else 46 | { 47 | Console.WriteLine("Set your access token as IPINFO_TOKEN in environment variables in order to run this sample code. You can also set your token string in the code manually."); 48 | return; 49 | } 50 | } 51 | 52 | private static string PromptHelper() 53 | { 54 | Console.WriteLine("\nOptions:"); 55 | Console.WriteLine("-Enter 0 to quit"); 56 | Console.WriteLine("-Enter ip address:"); 57 | return Console.ReadLine()??""; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /samples/HttpClientConfigurationSample/HttpClientConfigurationSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exe 9 | net6.0 10 | enable 11 | enable 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /samples/HttpClientConfigurationSample/Program.cs: -------------------------------------------------------------------------------- 1 | using IPinfo; 2 | using IPinfo.Models; 3 | 4 | namespace ConsoleApp 5 | { 6 | class Program 7 | { 8 | static async Task Main(string[] args) 9 | { 10 | Console.WriteLine("\nSample for changing configuration for http client"); 11 | 12 | // to use this sample, add your IPinfo Access Token to environment variable 13 | // named "IPINFO_TOKEN", or initialize your token string directly. 14 | string? token = Environment.GetEnvironmentVariable("IPINFO_TOKEN"); 15 | 16 | TimeSpan timeOut = TimeSpan.FromSeconds(5); 17 | HttpClient httpClient = new HttpClient(); 18 | 19 | if(token is not null) 20 | { 21 | // initializing IPinfo client 22 | IPinfoClient client = new IPinfoClient.Builder() 23 | .AccessToken(token) // pass your token string 24 | .HttpClientConfig(config => config 25 | .Timeout(timeOut) // pass timeout as TimeSpan 26 | .HttpClientInstance(httpClient)) // pass your own HttpClient instance 27 | .Build(); 28 | 29 | string ip = PromptHelper(); 30 | while(!ip.Equals("0")) 31 | { 32 | // making API call 33 | IPResponse ipResponse = await client.IPApi.GetDetailsAsync(ip); 34 | 35 | Console.WriteLine($"IPResponse.Bogon: {ipResponse.Bogon}"); 36 | Console.WriteLine($"IPResponse.IP: {ipResponse.IP}"); 37 | if (!ipResponse.Bogon) 38 | { 39 | Console.WriteLine($"IPResponse.City: {ipResponse.City}"); 40 | Console.WriteLine($"IPResponse.Company.Name: {ipResponse.Company.Name}"); 41 | Console.WriteLine($"IPResponse.Country: {ipResponse.Country}"); 42 | Console.WriteLine($"IPResponse.CountryName: {ipResponse.CountryName}"); 43 | } 44 | ip = PromptHelper(); 45 | } 46 | } 47 | else 48 | { 49 | Console.WriteLine("Set your access token as IPINFO_TOKEN in environment variables in order to run this sample code. You can also set your token string in the code manually."); 50 | return; 51 | } 52 | } 53 | 54 | private static string PromptHelper() 55 | { 56 | Console.WriteLine("\nOptions:"); 57 | Console.WriteLine("-Enter 0 to quit"); 58 | Console.WriteLine("-Enter ip address:"); 59 | return Console.ReadLine()??""; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /samples/SynchronousCallSample/Program.cs: -------------------------------------------------------------------------------- 1 | using IPinfo; 2 | using IPinfo.Models; 3 | 4 | namespace ConsoleApp 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | Console.WriteLine("\nSample for using synchronous call"); 11 | 12 | // to use this sample, add your IPinfo Access Token to environment variable 13 | // named "IPINFO_TOKEN", or initialize your token string directly. 14 | string? token = Environment.GetEnvironmentVariable("IPINFO_TOKEN"); 15 | 16 | TimeSpan timeOut = TimeSpan.FromSeconds(5); 17 | HttpClient httpClient = new HttpClient(); 18 | 19 | if(token is not null) 20 | { 21 | // initializing IPinfo client 22 | IPinfoClient client = new IPinfoClient.Builder() 23 | .AccessToken(token) // pass your token string 24 | .Build(); 25 | 26 | string ip = PromptHelper(); 27 | while(!ip.Equals("0")) 28 | { 29 | // making synchronous API call 30 | IPResponse ipResponse = client.IPApi.GetDetails(ip); 31 | 32 | Console.WriteLine($"IPResponse.IP: {ipResponse.IP}"); 33 | Console.WriteLine($"IPResponse.City: {ipResponse.City}"); 34 | Console.WriteLine($"IPResponse.Company.Name: {ipResponse.Company.Name}"); 35 | Console.WriteLine($"IPResponse.Country: {ipResponse.Country}"); 36 | Console.WriteLine($"IPResponse.CountryName: {ipResponse.CountryName}"); 37 | Console.WriteLine($"IPResponse.Loc: {ipResponse.Loc}"); 38 | Console.WriteLine($"IPResponse.Latitude: {ipResponse.Latitude}"); 39 | Console.WriteLine($"IPResponse.Longitude: {ipResponse.Longitude}"); 40 | if(ipResponse.Carrier != null) 41 | { 42 | // carrier info may not be available for every ip 43 | // for sample case, response for ip 66.87.125.72 has carrier info 44 | Console.WriteLine($"IPResponse.Carrier.Mcc: {ipResponse.Carrier.Mcc}"); 45 | Console.WriteLine($"IPResponse.Carrier.Mnc: {ipResponse.Carrier.Mnc}"); 46 | Console.WriteLine($"IPResponse.Carrier.Name: {ipResponse.Carrier.Name}"); 47 | } 48 | 49 | ip = PromptHelper(); 50 | } 51 | } 52 | else 53 | { 54 | Console.WriteLine("Set your access token as IPINFO_TOKEN in environment variables in order to run this sample code. You can also set your token string in the code manually."); 55 | return; 56 | } 57 | } 58 | 59 | private static string PromptHelper() 60 | { 61 | Console.WriteLine("\nOptions:"); 62 | Console.WriteLine("-Enter 0 to quit"); 63 | Console.WriteLine("-Enter ip address:"); 64 | return Console.ReadLine()??""; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /samples/SynchronousCallSample/SynchronousCallSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exe 9 | net6.0 10 | enable 11 | enable 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/IPinfo/Apis/BaseApi.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Collections.Generic; 6 | 7 | using IPinfo.Http.Client; 8 | using IPinfo.Http.Request; 9 | using IPinfo.Exceptions; 10 | using IPinfo.Cache; 11 | 12 | namespace IPinfo.Apis 13 | { 14 | /// 15 | /// The base class for all api classes. 16 | /// 17 | public class BaseApi 18 | { 19 | private const string DefaultBaseUrl = "https://ipinfo.io/"; 20 | private const string DefaultBaseUrlIPv6 = "https://v6.ipinfo.io/"; 21 | 22 | // version is appended in the user agent header, need to update when releasing new version 23 | private const string DefaultUserAgent = "IPinfoClient/C#/2.1.1"; 24 | 25 | /// 26 | /// HttpClient instance. 27 | /// 28 | private readonly IHttpClient _httpClient; 29 | 30 | /// 31 | /// CacheHandler instance. 32 | /// 33 | private readonly CacheHandler _cacheHandler; 34 | 35 | /// 36 | /// Initializes a new instance of the class. 37 | /// 38 | /// HttpClient for the API. 39 | /// Token for the API. 40 | internal BaseApi( 41 | IHttpClient httpClient, 42 | string token, 43 | CacheHandler cacheHandler) 44 | { 45 | this._httpClient = httpClient; 46 | this.Token = token; 47 | this._cacheHandler = cacheHandler; 48 | } 49 | 50 | /// 51 | /// Gets Token instance. 52 | /// 53 | internal string Token { get; } 54 | 55 | /// 56 | /// Gets User-Agent header value. 57 | /// 58 | internal string UserAgent => DefaultUserAgent; 59 | 60 | /// 61 | /// Gets base url values. 62 | /// 63 | internal string BaseUrl => DefaultBaseUrl; 64 | internal string BaseUrlIPv6 => DefaultBaseUrlIPv6; 65 | 66 | /// 67 | /// Get default HTTP client instance. 68 | /// 69 | /// IHttpClient. 70 | internal IHttpClient GetClientInstance() 71 | { 72 | return this._httpClient; 73 | } 74 | 75 | /// 76 | /// Validates the response against HTTP errors defined at the API level. 77 | /// 78 | /// Context of the request and the recieved response. 79 | internal void ValidateResponse(HttpContext context) 80 | { 81 | // [429] = Request Quota Exceeded Exception 82 | if (context.Response.StatusCode == 429) 83 | { 84 | throw new RequestQuotaExceededException(context); 85 | } 86 | 87 | // ensure success, this should be at the bottom of all the failure checks 88 | if (!context.Response.OriginalResponseMessage.IsSuccessStatusCode) 89 | { 90 | // request failed 91 | try 92 | { 93 | context.Response.OriginalResponseMessage.EnsureSuccessStatusCode(); 94 | } 95 | catch (HttpRequestException ex) 96 | { 97 | throw new RequestFailedGeneralException(context, ex); 98 | } 99 | } 100 | } 101 | 102 | /// 103 | /// Creates Get request for given url with default header settings. 104 | /// 105 | /// Context of the request and the recieved response. 106 | /// HttpRequest. 107 | internal HttpRequest CreateGetRequest(string queryUrl) 108 | { 109 | // append request with appropriate default headers. 110 | var headers = new Dictionary() 111 | { 112 | { "user-agent", this.UserAgent }, 113 | { "accept", "application/json" }, 114 | }; 115 | 116 | // prepare the API call request to fetch the response. 117 | HttpRequest httpRequest = this._httpClient.Get(queryUrl, headers, this.Token); 118 | return httpRequest; 119 | } 120 | 121 | /// 122 | /// Tells if cache is enabled. 123 | /// 124 | /// True if cache is enabled. 125 | private bool IsCacheEnabled() 126 | { 127 | if (this._cacheHandler != null) 128 | { 129 | return true; 130 | } 131 | else 132 | { 133 | return false; 134 | } 135 | } 136 | 137 | /// 138 | /// Gets item from cache using the CacheHandler. Returns null if cache is not available or item is not found. 139 | /// 140 | /// The key against wihich cache item needs to be returned. 141 | internal object GetFromCache(string key) 142 | { 143 | if (!IsCacheEnabled()) 144 | { 145 | return null; 146 | } 147 | return this._cacheHandler.Get(key); 148 | } 149 | 150 | /// 151 | /// Sets item in cache using the CacheHandler if the cache is available. 152 | /// 153 | /// The key against wihich item needs to be saved in the cache. 154 | /// The item that needs to be saved in the cache. 155 | internal void SetInCache(string key, object item) 156 | { 157 | if (IsCacheEnabled()) 158 | { 159 | this._cacheHandler.Set(key, item); 160 | } 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/IPinfo/Apis/IPApi.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using System.Threading; 6 | 7 | using IPinfo.Utilities; 8 | using IPinfo.Http.Client; 9 | using IPinfo.Http.Request; 10 | using IPinfo.Models; 11 | using IPinfo.Http.Response; 12 | using IPinfo.Cache; 13 | 14 | namespace IPinfo.Apis 15 | { 16 | /// 17 | /// IPApi. 18 | /// 19 | public sealed class IPApi : BaseApi 20 | { 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | /// httpClient. 25 | /// token. 26 | internal IPApi(IHttpClient httpClient, string token, CacheHandler cacheHandler) 27 | : base(httpClient, token, cacheHandler) 28 | { 29 | } 30 | 31 | /// 32 | /// Retrieves details of an IP address. 33 | /// 34 | /// The IP address of the user to retrieve details for. 35 | /// Returns the Models.IPResponse response from the API call. 36 | public Models.IPResponse GetDetails( 37 | IPAddress ipAddress) 38 | { 39 | string ipString = ipAddress?.ToString(); 40 | return this.GetDetails(ipString); 41 | } 42 | 43 | /// 44 | /// Retrieves details of an IP address. 45 | /// 46 | /// The IP address of the user to retrieve details for. 47 | /// Returns the Models.IPResponse response from the API call. 48 | public Models.IPResponse GetDetails( 49 | string ipAddress = "") 50 | { 51 | Task t = this.GetDetailsAsync(ipAddress); 52 | ApiHelper.RunTaskSynchronously(t); 53 | return t.Result; 54 | } 55 | 56 | /// 57 | /// Retrieves details of an IPv6 address. 58 | /// 59 | /// The IPv6 address of the user to retrieve details for. 60 | /// Returns the Models.IPResponse response from the API call. 61 | public Models.IPResponse GetDetailsV6(IPAddress ipv6Address) 62 | { 63 | string ipString = ipv6Address?.ToString(); 64 | return this.GetDetailsV6(ipString); 65 | } 66 | 67 | /// 68 | /// Retrieves details of an IPv6 address. 69 | /// 70 | /// The IPv6 address of the user to retrieve details for. 71 | /// Returns the Models.IPResponse response from the API call. 72 | public Models.IPResponse GetDetailsV6(string ipv6Address) 73 | { 74 | Task t = this.GetDetailsV6Async(ipv6Address); 75 | ApiHelper.RunTaskSynchronously(t); 76 | return t.Result; 77 | } 78 | 79 | /// 80 | /// Retrieves details of an IP address. 81 | /// 82 | /// The IP address of the user to retrieve details for. 83 | /// Cancellation token if the request is cancelled. 84 | /// Returns the Models.IPResponse response from the API call. 85 | public Task GetDetailsAsync( 86 | IPAddress ipAddress, 87 | CancellationToken cancellationToken = default) 88 | { 89 | string ipString = ipAddress?.ToString(); 90 | return this.GetDetailsAsync(ipString, cancellationToken); 91 | } 92 | 93 | /// 94 | /// Retrieves details of an IPv6 address. 95 | /// 96 | /// The IPv6 address of the user to retrieve details for. 97 | /// Cancellation token if the request is cancelled. 98 | /// Returns the Models.IPResponse response from the API call. 99 | public Task GetDetailsV6Async( 100 | IPAddress ipv6Address, 101 | CancellationToken cancellationToken = default) 102 | { 103 | string ipString = ipv6Address?.ToString(); 104 | return this.GetDetailsV6Async(ipString, cancellationToken); 105 | } 106 | 107 | 108 | /// 109 | /// Retrieves details of an IP address. 110 | /// 111 | /// The IP address of the user to retrieve details for. 112 | /// Cancellation token if the request is cancelled. 113 | /// Returns the Models.IPResponse response from the API call. 114 | public async Task GetDetailsAsync( 115 | string ipAddress = "", 116 | CancellationToken cancellationToken = default) 117 | { 118 | if (ipAddress == null) 119 | { 120 | ipAddress = ""; 121 | } 122 | // first check the data in the cache if cache is available 123 | IPResponse ipResponse = (IPResponse)GetFromCache(ipAddress); 124 | if (ipResponse != null) 125 | { 126 | return ipResponse; 127 | } 128 | 129 | if (BogonHelper.IsBogon(ipAddress)) 130 | { 131 | ipResponse = new IPResponse() 132 | { 133 | IP = ipAddress, 134 | Bogon = true 135 | }; 136 | return ipResponse; 137 | } 138 | 139 | // Determine the base URI based on IP version 140 | string baseUri = this.BaseUrl; 141 | 142 | // prepare query string for API call. 143 | StringBuilder queryBuilder = new StringBuilder(baseUri); 144 | queryBuilder.Append("{ip_address}"); 145 | // process optional template parameters. 146 | ApiHelper.AppendUrlWithTemplateParameters(queryBuilder, new Dictionary() 147 | { 148 | { "ip_address", ipAddress }, 149 | }); 150 | 151 | // prepare the API call request to fetch the response. 152 | HttpRequest httpRequest = this.CreateGetRequest(queryBuilder.ToString()); 153 | 154 | // invoke request and get response. 155 | HttpStringResponse response = await this.GetClientInstance().ExecuteAsStringAsync(httpRequest, cancellationToken).ConfigureAwait(false); 156 | HttpContext context = new HttpContext(httpRequest, response); 157 | 158 | // handle errors defined at the API level. 159 | this.ValidateResponse(context); 160 | 161 | var responseModel = JsonHelper.ParseIPResponse(response.Body); 162 | 163 | SetInCache(ipAddress, responseModel); 164 | return responseModel; 165 | } 166 | 167 | /// 168 | /// Retrieves details of an IPv6 address. 169 | /// 170 | /// The IPv6 address of the user to retrieve details for. 171 | /// Cancellation token if the request is cancelled. 172 | /// Returns the Models.IPResponse response from the API call. 173 | public async Task GetDetailsV6Async( 174 | string ipv6Address, 175 | CancellationToken cancellationToken = default) 176 | { 177 | if (ipv6Address == null) 178 | { 179 | ipv6Address = ""; 180 | } 181 | // Check the data in the cache if cache is available 182 | IPResponse ipResponse = (IPResponse)GetFromCache(ipv6Address); 183 | if (ipResponse != null) 184 | { 185 | return ipResponse; 186 | } 187 | 188 | if (BogonHelper.IsBogon(ipv6Address)) 189 | { 190 | ipResponse = new IPResponse() 191 | { 192 | IP = ipv6Address, 193 | Bogon = true 194 | }; 195 | return ipResponse; 196 | } 197 | 198 | // Use the IPv6 base URI for API requests. 199 | string baseUri = this.BaseUrlIPv6; 200 | 201 | // Prepare query string for API call. 202 | StringBuilder queryBuilder = new StringBuilder(baseUri); 203 | queryBuilder.Append("{ip_address}"); 204 | // Process optional template parameters. 205 | ApiHelper.AppendUrlWithTemplateParameters(queryBuilder, new Dictionary() 206 | { 207 | { "ip_address", ipv6Address }, 208 | }); 209 | 210 | // Prepare the API call request to fetch the response. 211 | HttpRequest httpRequest = this.CreateGetRequest(queryBuilder.ToString()); 212 | 213 | // Invoke request and get response. 214 | HttpStringResponse response = await this.GetClientInstance().ExecuteAsStringAsync(httpRequest, cancellationToken).ConfigureAwait(false); 215 | HttpContext context = new HttpContext(httpRequest, response); 216 | 217 | // Handle errors defined at the API level. 218 | this.ValidateResponse(context); 219 | 220 | var responseModel = JsonHelper.ParseIPResponse(response.Body); 221 | 222 | SetInCache(ipv6Address, responseModel); 223 | return responseModel; 224 | } 225 | 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/IPinfo/Cache/CacheConfigurations.cs: -------------------------------------------------------------------------------- 1 | namespace IPinfo.Cache 2 | { 3 | public class CacheConfigurations 4 | { 5 | // The default max size of the cache in mbs. 6 | public const int DefaultCacheMaxSizeMbs = 1; 7 | 8 | // The default time to live of the cache entry in seconds 9 | public const long DefaultCacheTtl = 60 * 60 * 24; 10 | 11 | /// 12 | /// Initializes a new instance of the 13 | /// class. 14 | /// 15 | private CacheConfigurations( 16 | int cacheMbs, 17 | long cacheTtl) 18 | { 19 | this.CacheMaxMbs = cacheMbs; 20 | this.CacheTtl = cacheTtl; 21 | } 22 | 23 | /// 24 | /// Gets max size of the cache in mbs. 25 | /// 26 | public int CacheMaxMbs { get; } 27 | 28 | /// 29 | /// Gets time to live of the cache entry in seconds. 30 | /// 31 | public long CacheTtl { get; } 32 | 33 | /// 34 | /// Builder class. 35 | /// 36 | public class Builder 37 | { 38 | private int _cacheMbs = DefaultCacheMaxSizeMbs; 39 | private long _cacheTtl = DefaultCacheTtl; 40 | 41 | /// 42 | /// Sets CacheMaxMbs. 43 | /// 44 | /// Cache size in MBs. 45 | /// Builder. 46 | public Builder CacheMaxMbs(int cacheMbs) 47 | { 48 | this._cacheMbs = cacheMbs; 49 | return this; 50 | } 51 | 52 | /// 53 | /// Sets the Cache time to live. 54 | /// 55 | /// Cache entry time to live in seconds. 56 | /// Builder. 57 | public Builder CacheTtl(long cacheTtl) 58 | { 59 | this._cacheTtl = cacheTtl; 60 | return this; 61 | } 62 | 63 | /// 64 | /// Creates an object of the CacheConfigurations using the values provided for the builder. 65 | /// 66 | /// CacheConfigurations. 67 | public CacheConfigurations Build() 68 | { 69 | return new CacheConfigurations( 70 | this._cacheMbs, 71 | this._cacheTtl); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/IPinfo/Cache/CacheHandler.cs: -------------------------------------------------------------------------------- 1 | namespace IPinfo.Cache 2 | { 3 | /// 4 | /// The CacheHandler will handle the cache operations. 5 | /// 6 | internal sealed class CacheHandler 7 | { 8 | // Implementation of ICache 9 | private ICache _cacheImplmentation; 10 | 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// any custom ICache implemetation. 15 | internal CacheHandler(ICache cacheImplmentation) 16 | { 17 | this._cacheImplmentation = cacheImplmentation; 18 | } 19 | 20 | /// 21 | /// Initializes a new instance of the class with default cache. 22 | /// 23 | internal CacheHandler() : this(new CacheWrapper()) 24 | { 25 | } 26 | 27 | /// 28 | /// Returns cache implementation being used. 29 | /// 30 | internal ICache Cache { get => _cacheImplmentation; } 31 | 32 | /// 33 | /// Returns cache entry against given key. 34 | /// 35 | /// A unique identifier for the cache entry to be returned. 36 | /// An object that is identified by key, if the entry exists; otherwise, null. 37 | internal object Get(string key) 38 | { 39 | return _cacheImplmentation.Get(key); 40 | } 41 | 42 | /// 43 | /// If the specified entry does not exist, it is created. If the specified entry exists, it is updated. 44 | /// 45 | /// A unique identifier for cache entry. 46 | /// The data for a cache entry. 47 | internal void Set(string key, object value) 48 | { 49 | _cacheImplmentation.Set(key, value); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/IPinfo/Cache/CacheWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Caching; 3 | using System.Collections.Specialized; 4 | 5 | namespace IPinfo.Cache 6 | { 7 | /// 8 | /// CacheWrapper for MemoryCache implementing ICache 9 | /// 10 | public sealed class CacheWrapper : ICache 11 | { 12 | private const string IPinfoCacheName = "IPinfoCache"; 13 | 14 | // IMPORTANT: Version of cache, needs to be updated when launching such new version of library 15 | // which incorporates changes in structure of json response being returned. 16 | private const string CacheKeyVsn = "1"; 17 | private MemoryCache _memoryCache; 18 | private CacheConfigurations _config; 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | public CacheWrapper():this(new CacheConfigurations.Builder().Build()) 24 | { 25 | } 26 | 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | /// configuration for . 31 | public CacheWrapper(CacheConfigurations config) 32 | { 33 | InitMemoryCache(config); 34 | } 35 | 36 | /// 37 | /// Initializes a new instance of the class. 38 | /// 39 | /// Action. 40 | /// Builder. 41 | public CacheWrapper(Action action) 42 | { 43 | if (action is null) 44 | { 45 | throw new ArgumentNullException(nameof(action)); 46 | } 47 | 48 | CacheConfigurations.Builder configBuilder = new CacheConfigurations.Builder(); 49 | action(configBuilder); 50 | 51 | InitMemoryCache(configBuilder.Build()); 52 | } 53 | 54 | /// 55 | /// Initializes memory cache. 56 | /// 57 | /// configuration for . 58 | private void InitMemoryCache(CacheConfigurations config) 59 | { 60 | this._config = config ?? throw new ArgumentNullException(nameof(config)); 61 | 62 | _memoryCache = new MemoryCache( 63 | IPinfoCacheName, 64 | new NameValueCollection 65 | { 66 | { "CacheMemoryLimitMegabytes", Convert.ToString(this._config.CacheMaxMbs) } 67 | } 68 | ); 69 | } 70 | 71 | /// 72 | /// Returns cache entry against given key. 73 | /// 74 | /// A unique identifier for the cache entry to return. 75 | /// An object that is identified by key, if the entry exists; otherwise, null. 76 | public object Get(string key) 77 | { 78 | return _memoryCache.Get(VersionedCacheKey(key)); 79 | } 80 | 81 | /// 82 | /// If the specified entry does not exist, it is created. If the specified entry exists, it is updated. 83 | /// 84 | /// A unique identifier for cache entry. 85 | /// The data for a cache entry. 86 | public void Set(string key, object value) 87 | { 88 | var cacheItemPolicy = new CacheItemPolicy 89 | { 90 | AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(_config.CacheTtl) 91 | }; 92 | _memoryCache.Set(VersionedCacheKey(key), value, cacheItemPolicy); 93 | } 94 | 95 | /// 96 | /// Transforms a key into a versioned cache key. 97 | /// 98 | /// The key to be converted into versioned key. 99 | /// The versioned key of the provided key. 100 | private string VersionedCacheKey(string key) 101 | { 102 | return $"{key}:{CacheKeyVsn}"; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/IPinfo/Cache/ICache.cs: -------------------------------------------------------------------------------- 1 | namespace IPinfo.Cache 2 | { 3 | /// 4 | /// ICache. 5 | /// 6 | public interface ICache 7 | { 8 | /// 9 | /// Returns cache entry against given key. 10 | /// 11 | /// A unique identifier for the cache entry to return. 12 | /// An object that is identified by key, if the entry exists; otherwise, null. 13 | object Get(string key); 14 | 15 | /// 16 | /// If the specified entry does not exist, it is created. If the specified entry exists, it is updated. 17 | /// 18 | /// A unique identifier for cache entry. 19 | /// The data for a cache entry. 20 | void Set(string key, object value); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/IPinfo/Exceptions/BaseApiException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using IPinfo.Http.Client; 3 | 4 | namespace IPinfo.Exceptions 5 | { 6 | /// 7 | /// This is the class for all exceptions that represent an error response from the server. 8 | /// 9 | public class BaseApiException : Exception 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The reason for throwing exception. 15 | /// The HTTP context that encapsulates request and response objects. 16 | public BaseApiException(string reason, HttpContext context) 17 | : base(reason) 18 | { 19 | this.HttpContext = context; 20 | } 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// The reason for throwing exception. 26 | /// The HTTP context that encapsulates request and response objects. 27 | /// The inner exception. 28 | public BaseApiException(string reason, HttpContext context, Exception innerException) 29 | : base(reason, innerException) 30 | { 31 | this.HttpContext = context; 32 | } 33 | 34 | /// 35 | /// Gets the HTTP response code from the API request. 36 | /// 37 | public int ResponseCode 38 | { 39 | get { return this.HttpContext.Response.StatusCode; } 40 | } 41 | 42 | /// 43 | /// Gets or sets the HttpContext for the request and response. 44 | /// 45 | public HttpContext HttpContext { get; internal set; } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/IPinfo/Exceptions/RequestFailedGeneralException.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | 3 | using IPinfo.Http.Client; 4 | 5 | namespace IPinfo.Exceptions 6 | { 7 | /// 8 | /// This is the class for RequestFailedGeneralException that represent general error on the api request. 9 | /// 10 | public class RequestFailedGeneralException : BaseApiException 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The HTTP context that encapsulates request and response objects. 16 | /// The inner HttpRequestException. 17 | public RequestFailedGeneralException(HttpContext context, HttpRequestException httpRequestException) 18 | : base(context.Response.GetResponseDetailsForException(), context, httpRequestException) 19 | { 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/IPinfo/Exceptions/RequestQuotaExceededException.cs: -------------------------------------------------------------------------------- 1 | using IPinfo.Http.Client; 2 | 3 | namespace IPinfo.Exceptions 4 | { 5 | /// 6 | /// This is the class for RequestQuotaExceededException that represent an error code 429 from the server. 7 | /// 8 | public class RequestQuotaExceededException : BaseApiException 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The HTTP context that encapsulates request and response objects. 14 | public RequestQuotaExceededException(HttpContext context) 15 | : base("You have been sending too many requests. Visit https://ipinfo.io/account to see your API limits.", context) 16 | { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/IPinfo/Http/Client/HttpClientConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | 4 | namespace IPinfo.Http.Client 5 | { 6 | /// 7 | /// HttpClientConfiguration represents the current state of the Http Client. 8 | /// 9 | public class HttpClientConfiguration : IHttpClientConfiguration 10 | { 11 | /// 12 | /// Initializes a new instance of the 13 | /// class. 14 | /// 15 | private HttpClientConfiguration( 16 | TimeSpan timeout, 17 | HttpClient httpClientInstance, 18 | bool overrideHttpClientConfiguration) 19 | { 20 | this.Timeout = timeout; 21 | this.HttpClientInstance = httpClientInstance; 22 | this.OverrideHttpClientConfiguration = overrideHttpClientConfiguration; 23 | } 24 | 25 | /// 26 | /// Gets Http client timeout. 27 | /// 28 | public TimeSpan Timeout { get; } 29 | 30 | /// 31 | /// Gets HttpClient instance used to make the HTTP calls 32 | /// 33 | public HttpClient HttpClientInstance { get; } 34 | 35 | /// 36 | /// Gets Boolean which allows the SDK to override http client instance's settings used for features like timeouts etc. 37 | /// 38 | public bool OverrideHttpClientConfiguration { get; } 39 | 40 | /// 41 | public override string ToString() 42 | { 43 | return "HttpClientConfiguration: " + 44 | $"{this.Timeout} , " + 45 | $"{this.HttpClientInstance} , " + 46 | $"{this.OverrideHttpClientConfiguration} "; 47 | } 48 | 49 | /// 50 | /// Builder class. 51 | /// 52 | public class Builder 53 | { 54 | private TimeSpan _timeout = TimeSpan.FromSeconds(60); 55 | private HttpClient _httpClientInstance = new HttpClient(); 56 | private bool _overrideHttpClientConfiguration = true; 57 | 58 | /// 59 | /// Sets the Timeout. 60 | /// 61 | /// Timeout. 62 | /// Builder. 63 | public Builder Timeout(TimeSpan timeout) 64 | { 65 | this._timeout = timeout.TotalSeconds <= 0 ? TimeSpan.FromSeconds(60) : timeout; 66 | return this; 67 | } 68 | 69 | /// 70 | /// Sets the HttpClientInstance. 71 | /// 72 | /// HttpClientInstance. 73 | /// Override HttpClient configurations. 74 | /// Builder. 75 | public Builder HttpClientInstance(HttpClient httpClientInstance, bool overrideHttpClientConfiguration = true) 76 | { 77 | this._httpClientInstance = httpClientInstance ?? new HttpClient(); 78 | this._overrideHttpClientConfiguration = overrideHttpClientConfiguration; 79 | return this; 80 | } 81 | 82 | /// 83 | /// Creates an object of the HttpClientConfiguration using the values provided for the builder. 84 | /// 85 | /// HttpClientConfiguration. 86 | public HttpClientConfiguration Build() 87 | { 88 | return new HttpClientConfiguration( 89 | this._timeout, 90 | this._httpClientInstance, 91 | this._overrideHttpClientConfiguration); 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/IPinfo/Http/Client/HttpClientWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Net.Http.Headers; 4 | using System.Threading.Tasks; 5 | using System.Threading; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | 10 | using IPinfo.Models; 11 | using IPinfo.Utilities; 12 | using IPinfo.Http.Request; 13 | using IPinfo.Http.Response; 14 | 15 | namespace IPinfo.Http.Client 16 | { 17 | /// 18 | /// HttpClientWrapper. 19 | /// 20 | internal sealed class HttpClientWrapper : IHttpClient 21 | { 22 | private HttpClient _client; 23 | private bool _overrideHttpClientConfiguration; 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// HttpClientConfiguration object. 29 | public HttpClientWrapper(HttpClientConfiguration httpClientConfig) 30 | { 31 | this._client = httpClientConfig.HttpClientInstance; 32 | this._overrideHttpClientConfiguration = httpClientConfig.OverrideHttpClientConfiguration; 33 | 34 | if (_overrideHttpClientConfiguration) 35 | { 36 | this._client.Timeout = httpClientConfig.Timeout; 37 | } 38 | } 39 | 40 | /// 41 | /// Executes the http request. 42 | /// 43 | /// Http request. 44 | /// HttpStringResponse. 45 | public HttpStringResponse ExecuteAsString(HttpRequest request) 46 | { 47 | Task t = this.ExecuteAsStringAsync(request); 48 | ApiHelper.RunTaskSynchronously(t); 49 | return t.Result; 50 | } 51 | 52 | /// 53 | /// Executes the http request asynchronously. 54 | /// 55 | /// Http request. 56 | /// cancellationToken. 57 | /// Returns the HttpStringResponse. 58 | public async Task ExecuteAsStringAsync( 59 | HttpRequest request, 60 | CancellationToken cancellationToken = default) 61 | { 62 | HttpResponseMessage responseMessage = await this.Execute(request, cancellationToken).ConfigureAwait(false); 63 | int statusCode = (int)responseMessage.StatusCode; 64 | var headers = GetCombinedResponseHeaders(responseMessage); 65 | Stream rawBody = await responseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false); 66 | string body = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); 67 | 68 | var response = new HttpStringResponse(statusCode, headers, rawBody, body, responseMessage); 69 | 70 | return response; 71 | } 72 | 73 | /// 74 | /// Get http request. 75 | /// 76 | /// queryUrl. 77 | /// headers. 78 | /// token. 79 | /// queryParameters. 80 | /// HttpRequest. 81 | public HttpRequest Get( 82 | string queryUrl, 83 | Dictionary headers, 84 | string token = null, 85 | Dictionary queryParameters = null) 86 | { 87 | return new HttpRequest(HttpMethod.Get, queryUrl, headers, token, queryParameters: queryParameters); 88 | } 89 | 90 | /// 91 | /// Get http request. 92 | /// 93 | /// queryUrl. 94 | /// HttpRequest. 95 | public HttpRequest Get(string queryUrl) 96 | { 97 | return new HttpRequest(HttpMethod.Get, queryUrl); 98 | } 99 | 100 | private async Task Execute( 101 | HttpRequest request, 102 | CancellationToken cancellationToken) 103 | { 104 | HttpRequestMessage requestMessage = new HttpRequestMessage 105 | { 106 | RequestUri = new Uri(request.QueryUrl), 107 | Method = request.HttpMethod, 108 | }; 109 | 110 | if (request.Headers != null) 111 | { 112 | foreach (var headers in request.Headers) 113 | { 114 | requestMessage.Headers.TryAddWithoutValidation(headers.Key, headers.Value); 115 | } 116 | } 117 | 118 | if (!string.IsNullOrEmpty(request.Token)) 119 | { 120 | requestMessage.Headers.Authorization = new AuthenticationHeaderValue( 121 | "Bearer", request.Token); 122 | } 123 | 124 | return await this._client.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); 125 | } 126 | 127 | private static Dictionary GetCombinedResponseHeaders(HttpResponseMessage responseMessage) 128 | { 129 | var headers = responseMessage.Headers.ToDictionary(l => l.Key, k => k.Value.First()); 130 | if (responseMessage.Content != null) 131 | { 132 | foreach (var contentHeader in responseMessage.Content.Headers) 133 | { 134 | if (headers.ContainsKey(contentHeader.Key)) 135 | { 136 | continue; 137 | } 138 | 139 | headers.Add(contentHeader.Key, contentHeader.Value.First()); 140 | } 141 | } 142 | 143 | return headers; 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/IPinfo/Http/Client/HttpContext.cs: -------------------------------------------------------------------------------- 1 | using IPinfo.Http.Request; 2 | using IPinfo.Http.Response; 3 | 4 | namespace IPinfo.Http.Client 5 | { 6 | /// 7 | /// Represents the contextual information of HTTP request and response. 8 | /// 9 | public sealed class HttpContext 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The http request in the current context. 15 | /// The http response in the current context. 16 | public HttpContext(HttpRequest request, HttpResponse response) 17 | { 18 | this.Request = request; 19 | this.Response = response; 20 | } 21 | 22 | /// 23 | /// Gets the http request in the current context. 24 | /// 25 | public HttpRequest Request { get; } 26 | 27 | /// 28 | /// Gets the http response in the current context. 29 | /// 30 | public HttpResponse Response { get; } 31 | 32 | /// 33 | public override string ToString() 34 | { 35 | return $" Request = {this.Request}, " + 36 | $" Response = {this.Response}"; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/IPinfo/Http/Client/IHttpClient.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | 5 | using IPinfo.Http.Request; 6 | using IPinfo.Http.Response; 7 | 8 | namespace IPinfo.Http.Client 9 | { 10 | /// 11 | /// IHttpClient. 12 | /// 13 | internal interface IHttpClient 14 | { 15 | /// 16 | /// Execute a given HttpRequest to get string response back. 17 | /// 18 | /// The given HttpRequest to execute. 19 | /// HttpResponse containing raw information. 20 | HttpStringResponse ExecuteAsString(HttpRequest request); 21 | 22 | /// 23 | /// Execute a given HttpRequest to get async string response back. 24 | /// 25 | /// The given HttpRequest to execute. 26 | /// CancellationToken. 27 | /// HttpResponse containing raw information. 28 | Task ExecuteAsStringAsync(HttpRequest request, CancellationToken cancellationToken = default); 29 | 30 | /// 31 | /// Create a simple HTTP GET request given the URL. 32 | /// 33 | /// Url the request should be sent to. 34 | /// HttpRequest initialised with the url specified. 35 | HttpRequest Get(string queryUrl); 36 | 37 | /// 38 | /// Create a simple HTTP GET request given relavent parameters. 39 | /// 40 | /// Url the request should be sent to. 41 | /// HTTP headers that should be included. 42 | /// Auth token. 43 | /// Query parameters to be included. 44 | /// HttpRequest initialised with the http parameters specified. 45 | HttpRequest Get( 46 | string queryUrl, 47 | Dictionary headers, 48 | string token = null, 49 | Dictionary queryParameters = null); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/IPinfo/Http/Client/IHttpClientConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | 4 | namespace IPinfo.Http.Client 5 | { 6 | /// 7 | /// Represents the current state of the Http Client. 8 | /// 9 | public interface IHttpClientConfiguration 10 | { 11 | /// 12 | /// Http client timeout. 13 | /// 14 | TimeSpan Timeout { get; } 15 | 16 | HttpClient HttpClientInstance { get; } 17 | 18 | /// 19 | /// Boolean which allows the SDK to override http client instance's settings used for features like retries, timeouts etc. 20 | /// 21 | bool OverrideHttpClientConfiguration { get; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/IPinfo/Http/Request/HttpRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Http; 3 | using System.Linq; 4 | 5 | using IPinfo.Utilities; 6 | 7 | namespace IPinfo.Http.Request 8 | { 9 | /// 10 | /// HttpRequest stores necessary information about the http request. 11 | /// 12 | public sealed class HttpRequest 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// Http verb to use for the http request. 18 | /// The query url for the http request. 19 | public HttpRequest(HttpMethod method, string queryUrl) 20 | { 21 | this.HttpMethod = method; 22 | this.QueryUrl = queryUrl; 23 | } 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// Http verb to use for the http request. 29 | /// The query url for the http request. 30 | /// Headers to send with the request. 31 | /// Auth token. 32 | /// QueryParameters. 33 | public HttpRequest( 34 | HttpMethod method, 35 | string queryUrl, 36 | Dictionary headers, 37 | string token, 38 | Dictionary queryParameters = null) 39 | { 40 | this.HttpMethod = method; 41 | this.QueryUrl = queryUrl; 42 | this.QueryParameters = queryParameters; 43 | this.Headers = headers; 44 | this.Token = token; 45 | } 46 | 47 | /// 48 | /// Gets the HTTP verb to use for this request. 49 | /// 50 | public HttpMethod HttpMethod { get; } 51 | 52 | /// 53 | /// Gets the query url for the http request. 54 | /// 55 | public string QueryUrl { get; } 56 | 57 | /// 58 | /// Gets the query parameters collection for the current http request. 59 | /// 60 | public Dictionary QueryParameters { get; private set; } 61 | 62 | /// 63 | /// Gets the headers collection for the current http request. 64 | /// 65 | public Dictionary Headers { get; private set; } 66 | 67 | /// 68 | /// Gets the token for Auth. 69 | /// 70 | public string Token { get; } 71 | 72 | /// 73 | /// Concatenate values from a Dictionary to this object. 74 | /// 75 | /// headersToAdd. 76 | /// Dictionary. 77 | public Dictionary AddHeaders(Dictionary headersToAdd) 78 | { 79 | if (this.Headers == null) 80 | { 81 | this.Headers = new Dictionary(headersToAdd); 82 | } 83 | else 84 | { 85 | this.Headers = this.Headers.Concat(headersToAdd).ToDictionary(x => x.Key, x => x.Value); 86 | } 87 | 88 | return this.Headers; 89 | } 90 | 91 | /// 92 | /// Concatenate values from a Dictionary to query parameters dictionary. 93 | /// 94 | /// queryParamaters. 95 | public void AddQueryParameters(Dictionary queryParamaters) 96 | { 97 | if (this.QueryParameters == null) 98 | { 99 | this.QueryParameters = new Dictionary(queryParamaters); 100 | } 101 | else 102 | { 103 | this.QueryParameters = this.QueryParameters.Concat(queryParamaters).ToDictionary(x => x.Key, x => x.Value); 104 | } 105 | } 106 | 107 | /// 108 | public override string ToString() 109 | { 110 | return $" HttpMethod = {this.HttpMethod}, " + 111 | $" QueryUrl = {this.QueryUrl}, " + 112 | $" QueryParameters = {JsonHelper.Serialize(this.QueryParameters)}, " + 113 | $" Headers = {JsonHelper.Serialize(this.Headers)}, " + 114 | $" Token = {this.Token}"; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/IPinfo/Http/Response/HttpResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Net.Http; 4 | 5 | using IPinfo.Utilities; 6 | 7 | namespace IPinfo.Http.Response 8 | { 9 | /// 10 | /// HttpResponse stores necessary information about the http response. 11 | /// 12 | public class HttpResponse 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// statusCode. 18 | /// headers. 19 | /// rawBody. 20 | /// rawBody. 21 | public HttpResponse(int statusCode, Dictionary headers, Stream rawBody, HttpResponseMessage originalResponseMessage) 22 | { 23 | this.StatusCode = statusCode; 24 | this.Headers = headers; 25 | this.RawBody = rawBody; 26 | this.OriginalResponseMessage = originalResponseMessage; 27 | } 28 | 29 | /// 30 | /// Gets the HTTP Status code of the http response. 31 | /// 32 | public int StatusCode { get; } 33 | 34 | /// 35 | /// Gets the headers of the http response. 36 | /// 37 | public Dictionary Headers { get; } 38 | 39 | /// 40 | /// Gets the stream of the body. 41 | /// 42 | public Stream RawBody { get; } 43 | 44 | /// 45 | /// Gets the original response message. 46 | /// 47 | public HttpResponseMessage OriginalResponseMessage { get; } 48 | 49 | /// 50 | public override string ToString() 51 | { 52 | return $" StatusCode = {this.StatusCode}, " + 53 | $" Headers = {JsonHelper.Serialize(this.Headers)}, " + 54 | $" RawBody = {this.RawBody}"; 55 | } 56 | 57 | /// 58 | /// Gets the response details to be displayed in exception. 59 | /// 60 | public virtual string GetResponseDetailsForException() 61 | { 62 | return $" StatusCode = {this.StatusCode},\n" + 63 | $" RawBody = {this.RawBody}"; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/IPinfo/Http/Response/HttpStringResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Net.Http; 4 | 5 | namespace IPinfo.Http.Response 6 | { 7 | /// 8 | /// HttpStringResponse inherits from HttpResponse and has additional property 9 | /// of string body. 10 | /// 11 | public sealed class HttpStringResponse : HttpResponse 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// statusCode. 17 | /// headers. 18 | /// rawBody. 19 | /// body. 20 | /// body. 21 | public HttpStringResponse(int statusCode, Dictionary headers, Stream rawBody, string body, HttpResponseMessage originalResponseMessage) 22 | : base(statusCode, headers, rawBody, originalResponseMessage) 23 | { 24 | this.Body = body; 25 | } 26 | 27 | /// 28 | /// Gets the raw string body of the http response. 29 | /// 30 | public string Body { get; } 31 | 32 | /// 33 | public override string ToString() 34 | { 35 | return $"Body = {this.Body}" + 36 | $"{base.ToString()}: "; 37 | } 38 | 39 | /// 40 | public override string GetResponseDetailsForException() 41 | { 42 | return $" StatusCode = {this.StatusCode},\n" + 43 | $"Body = {this.Body}"; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/IPinfo/IPinfo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | IPinfo 6 | 3.0.1.0 7 | IPinfo 8 | IPinfo 9 | IPinfo 10 | IPinfo 11 | 3.0.1.0 12 | 3.0.1.0 13 | 7.3 14 | https://github.com/ipinfo/csharp 15 | git 16 | https://github.com/ipinfo/csharp.git 17 | icon.png 18 | https://raw.githubusercontent.com/ipinfo/csharp/main/src/IPinfo/icon.png 19 | True 20 | This is the official C#/.NET client library for the IPinfo.io IP address API 21 | Inlined the JSON data files into code. 22 | Copyright © 2022 ipinfo.io 23 | IPinfo;ip;location;geolocation 24 | README.md 25 | LICENSE 26 | true 27 | snupkg 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | <_Parameter1>IPinfo.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a3e25284f42d5a26fdeb222c8fb9ac5df0b7421bdf350a8e828c18f72f1ec06fec81eff098d3610ebd64ec3c3b9b66f3801fc3d38fde5968d04c46b319896074cdae04c20a5c4d2dc5438e259265617106567f7357b6daf0fbf301fbe43df5019c53c5668b231423e23411e6e4c4cfae2b0b2e0f6e1333e0fe2cd691c26717d4 44 | 45 | 46 | 47 | 48 | 49 | <_Parameter1>..\..\sgKeyIPinfoStrongName.snk 50 | 51 | 52 | 53 | 54 | 55 | <_Parameter1>true 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/IPinfo/IPinfo.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | $author$ 9 | $description$ 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/IPinfo/IPinfoClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using IPinfo.Http.Client; 4 | using IPinfo.Apis; 5 | using IPinfo.Cache; 6 | using IPinfo.Utilities; 7 | 8 | namespace IPinfo 9 | { 10 | /// 11 | /// The gateway for IPinfo SDK. This class holds the configuration of the SDK. 12 | /// 13 | public sealed class IPinfoClient 14 | { 15 | private readonly IHttpClient _httpClient; 16 | private readonly CacheHandler _cacheHandler; 17 | private readonly Lazy _ipApi; 18 | 19 | private IPinfoClient( 20 | string accessToken, 21 | IHttpClient httpClient, 22 | CacheHandler cacheHandler, 23 | IHttpClientConfiguration httpClientConfiguration) 24 | { 25 | this._httpClient = httpClient; 26 | this._cacheHandler = cacheHandler; 27 | this.HttpClientConfiguration = httpClientConfiguration; 28 | 29 | this._ipApi = new Lazy( 30 | () => new IPApi(this._httpClient, accessToken, cacheHandler)); 31 | } 32 | 33 | /// 34 | /// Gets IPApi. 35 | /// 36 | public IPApi IPApi => this._ipApi.Value; 37 | 38 | /// 39 | /// Gets the configuration of the Http Client associated with this client. 40 | /// 41 | public IHttpClientConfiguration HttpClientConfiguration { get; } 42 | 43 | /// 44 | /// Gets the configuration of the Http Client associated with this client. 45 | /// 46 | public ICache Cache { get => _cacheHandler?.Cache; } 47 | 48 | /// 49 | /// Builder class. 50 | /// 51 | public class Builder 52 | { 53 | private string _accessToken = ""; 54 | private HttpClientConfiguration.Builder _httpClientConfig = new HttpClientConfiguration.Builder(); 55 | private IHttpClient _httpClient; 56 | private CacheHandler _cacheHandler = new CacheHandler(); 57 | 58 | /// 59 | /// Sets credentials for BearerAuth. 60 | /// 61 | /// AccessToken. 62 | /// Builder. 63 | public Builder AccessToken(string accessToken) 64 | { 65 | this._accessToken = accessToken; 66 | return this; 67 | } 68 | 69 | /// 70 | /// Sets HttpClientConfig. 71 | /// 72 | /// Action. 73 | /// Builder. 74 | public Builder HttpClientConfig(Action action) 75 | { 76 | if (action is null) 77 | { 78 | throw new ArgumentNullException(nameof(action)); 79 | } 80 | 81 | action(this._httpClientConfig); 82 | return this; 83 | } 84 | 85 | /// 86 | /// Sets the ICache implementation for the Builder. 87 | /// 88 | /// ICache implementation. Pass null to disable the cache. 89 | /// Builder. 90 | public Builder Cache(ICache cache) 91 | { 92 | // Null is allowed here, which is being used to indicate that user do not want the cache. 93 | if(cache == null) 94 | { 95 | this._cacheHandler = null; 96 | } 97 | else 98 | { 99 | this._cacheHandler = new CacheHandler(cache); 100 | } 101 | return this; 102 | } 103 | 104 | /// 105 | /// Creates an object of the IPinfoClient using the values provided for the builder. 106 | /// 107 | /// IPinfoClient. 108 | public IPinfoClient Build() 109 | { 110 | this._httpClient = new HttpClientWrapper(this._httpClientConfig.Build()); 111 | 112 | return new IPinfoClient( 113 | this._accessToken, 114 | this._httpClient, 115 | this._cacheHandler, 116 | this._httpClientConfig.Build()); 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/IPinfo/Models/ASN.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace IPinfo.Models 4 | { 5 | public class ASN 6 | { 7 | public string Asn { get; } 8 | public string Domain { get; } 9 | public string Name { get; } 10 | public string Route { get; } 11 | public string Type { get; } 12 | 13 | // immutable type 14 | [JsonConstructor] 15 | public ASN(string asn, string domain, string name, string route, string type) => 16 | (Asn, Domain, Name, Route, Type) = (asn, domain, name, route, type); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/IPinfo/Models/Abuse.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace IPinfo.Models 4 | { 5 | public class Abuse 6 | { 7 | public string Address { get; } 8 | public string Country { get; } 9 | public string Email { get; } 10 | public string Name { get; } 11 | public string Network { get; } 12 | public string Phone { get; } 13 | 14 | // immutable type 15 | [JsonConstructor] 16 | public Abuse(string address, string country, string email, string name, string network, string phone) => 17 | (Address, Country, Email, Name, Network, Phone) = (address, country, email, name, network, phone); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/IPinfo/Models/Carrier.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace IPinfo.Models 4 | { 5 | public class Carrier 6 | { 7 | public string Mcc { get; } 8 | public string Mnc { get; } 9 | public string Name { get; } 10 | 11 | // immutable type 12 | [JsonConstructor] 13 | public Carrier(string mcc, string mnc, string name) => 14 | (Mcc, Mnc, Name) = (mcc, mnc, name); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/IPinfo/Models/Company.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace IPinfo.Models 4 | { 5 | public class Company 6 | { 7 | public string Domain { get; } 8 | public string Name { get; } 9 | public string Type { get; } 10 | 11 | // immutable type 12 | [JsonConstructor] 13 | public Company(string domain, string name, string type) => 14 | (Domain, Name, Type) = (domain, name, type); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/IPinfo/Models/Continent.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | namespace IPinfo.Models 3 | { 4 | /// 5 | /// Gets continent code and name. 6 | /// { "code": "AS", "name": "Asia"} 7 | /// 8 | public class Continent 9 | { 10 | [JsonInclude] 11 | public string Code { get; internal set; } 12 | 13 | [JsonInclude] 14 | public string Name { get; internal set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/IPinfo/Models/CountryCurrency.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | namespace IPinfo.Models 3 | { 4 | /// 5 | /// Gets country currency code and symbol. 6 | /// { "code": "PKR" ,"symbol": "₨"} 7 | /// 8 | public class CountryCurrency 9 | { 10 | [JsonInclude] 11 | public string Code { get; internal set; } 12 | 13 | [JsonInclude] 14 | public string Symbol { get; internal set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/IPinfo/Models/CountryFlag.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | namespace IPinfo.Models 3 | { 4 | /// 5 | /// Gets country flag emoji and unicode. 6 | /// {Emoji:"🇵🇰", Unicode:"U+1F1F5 U+1F1F0"} 7 | /// 8 | public class CountryFlag 9 | { 10 | [JsonInclude] 11 | public string Emoji { get; internal set; } 12 | 13 | [JsonInclude] 14 | public string Unicode { get; internal set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/IPinfo/Models/DomainsList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace IPinfo.Models 5 | { 6 | public class DomainsList 7 | { 8 | public List Domains { get; } 9 | public string IP { get; } 10 | public int Total { get; } 11 | 12 | // immutable type 13 | [JsonConstructor] 14 | public DomainsList(List domains, string ip, int total) => 15 | (Domains, IP, Total) = (domains, ip, total); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/IPinfo/Models/IPResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace IPinfo.Models 4 | { 5 | public class IPResponse 6 | { 7 | [JsonInclude] 8 | public bool Anycast { get; private set; } 9 | 10 | [JsonInclude] 11 | public bool Bogon { get; internal set; } 12 | 13 | [JsonInclude] 14 | public string City { get; private set; } 15 | 16 | [JsonInclude] 17 | public string Country { get; private set; } 18 | 19 | public string CountryName { get; internal set; } 20 | 21 | public bool IsEU { get; internal set; } 22 | 23 | public CountryFlag CountryFlag { get; internal set; } 24 | 25 | public string CountryFlagURL { get; internal set; } 26 | 27 | public CountryCurrency CountryCurrency { get; internal set; } 28 | 29 | public Continent Continent { get; internal set; } 30 | 31 | [JsonInclude] 32 | public string Hostname { get; private set; } 33 | 34 | [JsonInclude] 35 | public string Loc { get; private set; } 36 | 37 | public string Latitude { get; internal set; } 38 | 39 | public string Longitude { get; internal set; } 40 | 41 | [JsonInclude] 42 | public string Org { get; private set; } 43 | 44 | [JsonInclude] 45 | public string Postal { get; private set; } 46 | 47 | [JsonInclude] 48 | public string Region { get; private set; } 49 | 50 | [JsonInclude] 51 | public string Timezone { get; private set; } 52 | 53 | [JsonInclude] 54 | public string IP { get; internal set; } 55 | 56 | [JsonInclude] 57 | public ASN Asn { get; private set; } 58 | 59 | [JsonInclude] 60 | public Company Company{ get; private set; } 61 | 62 | [JsonInclude] 63 | public Carrier Carrier{ get; private set; } 64 | 65 | [JsonInclude] 66 | public Privacy Privacy{ get; private set; } 67 | 68 | [JsonInclude] 69 | public Abuse Abuse{ get; private set; } 70 | 71 | [JsonInclude] 72 | public DomainsList Domains{ get; private set; } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/IPinfo/Models/Privacy.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace IPinfo.Models 4 | { 5 | public class Privacy 6 | { 7 | public bool Hosting { get; } 8 | public bool Proxy { get; } 9 | public bool Relay { get; } 10 | public string Service { get; } 11 | public bool Tor { get; } 12 | public bool Vpn { get; } 13 | 14 | // immutable type 15 | [JsonConstructor] 16 | public Privacy(bool hosting, bool proxy, bool relay, string service, bool tor, bool vpn) => 17 | (Hosting, Proxy, Relay, Service, Tor, Vpn) = (hosting, proxy, relay, service, tor, vpn); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/IPinfo/Utilities/ApiHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Threading.Tasks; 4 | using System.Collections.Generic; 5 | 6 | namespace IPinfo.Utilities 7 | { 8 | /// 9 | /// ApiHelper class contains a bunch of helper methods. 10 | /// 11 | internal static class ApiHelper 12 | { 13 | /// 14 | /// Runs asynchronous tasks synchronously and throws the first caught exception. 15 | /// 16 | /// The task to be run synchronously. 17 | internal static void RunTaskSynchronously(Task t) 18 | { 19 | try 20 | { 21 | t.Wait(); 22 | } 23 | catch (AggregateException e) 24 | { 25 | if (e.InnerExceptions.Count > 0) 26 | { 27 | throw e.InnerExceptions[0]; 28 | } 29 | else 30 | { 31 | throw; 32 | } 33 | } 34 | } 35 | 36 | /// 37 | /// Replaces template parameters in the given url. 38 | /// 39 | /// The queryBuilder to replace the template parameters. 40 | /// The parameters to replace in the url. 41 | internal static void AppendUrlWithTemplateParameters(StringBuilder queryBuilder, IEnumerable> parameters) 42 | { 43 | // perform parameter validation 44 | if (queryBuilder == null) 45 | { 46 | throw new ArgumentNullException("queryBuilder"); 47 | } 48 | 49 | if (parameters == null) 50 | { 51 | return; 52 | } 53 | 54 | // iterate and replace parameters 55 | foreach (KeyValuePair pair in parameters) 56 | { 57 | string replaceValue = string.Empty; 58 | 59 | // load element value as string 60 | if (pair.Value == null) 61 | { 62 | replaceValue = string.Empty; 63 | } 64 | else 65 | { 66 | replaceValue = pair.Value.ToString(); 67 | } 68 | 69 | // convert string to escaped representation, e.g. replace space with %20 70 | replaceValue = Uri.EscapeDataString(replaceValue); 71 | 72 | // find the template parameter and replace it with its value 73 | queryBuilder.Replace(string.Format("{{{0}}}", pair.Key), replaceValue); 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/IPinfo/Utilities/BogonHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace IPinfo.Utilities 5 | { 6 | /// 7 | /// BogonHelper class contains bogon helper methods. 8 | /// 9 | internal static class BogonHelper 10 | { 11 | private static readonly IPNetwork[] s_bogonNetworks = 12 | { 13 | // IPv4 14 | new IPNetwork("0.0.0.0/8"), 15 | new IPNetwork("10.0.0.0/8"), 16 | new IPNetwork("100.64.0.0/10"), 17 | new IPNetwork("127.0.0.0/8"), 18 | new IPNetwork("169.254.0.0/16"), 19 | new IPNetwork("172.16.0.0/12"), 20 | new IPNetwork("192.0.0.0/24"), 21 | new IPNetwork("192.0.2.0/24"), 22 | new IPNetwork("192.168.0.0/16"), 23 | new IPNetwork("198.18.0.0/15"), 24 | new IPNetwork("198.51.100.0/24"), 25 | new IPNetwork("203.0.113.0/24"), 26 | new IPNetwork("224.0.0.0/4"), 27 | new IPNetwork("240.0.0.0/4"), 28 | new IPNetwork("255.255.255.255/32"), 29 | // IPv6 30 | new IPNetwork("::/128"), 31 | new IPNetwork("::1/128"), 32 | new IPNetwork("::ffff:0:0/96"), 33 | new IPNetwork("::/96"), 34 | new IPNetwork("100::/64"), 35 | new IPNetwork("2001:10::/28"), 36 | new IPNetwork("2001:db8::/32"), 37 | new IPNetwork("fc00::/7"), 38 | new IPNetwork("fe80::/10"), 39 | new IPNetwork("fec0::/10"), 40 | new IPNetwork("ff00::/8"), 41 | // 6to4 42 | new IPNetwork("2002::/24"), 43 | new IPNetwork("2002:a00::/24"), 44 | new IPNetwork("2002:7f00::/24"), 45 | new IPNetwork("2002:a9fe::/32"), 46 | new IPNetwork("2002:ac10::/28"), 47 | new IPNetwork("2002:c000::/40"), 48 | new IPNetwork("2002:c000:200::/40"), 49 | new IPNetwork("2002:c0a8::/32"), 50 | new IPNetwork("2002:c612::/31"), 51 | new IPNetwork("2002:c633:6400::/40"), 52 | new IPNetwork("2002:cb00:7100::/40"), 53 | new IPNetwork("2002:e000::/20"), 54 | new IPNetwork("2002:f000::/20"), 55 | new IPNetwork("2002:ffff:ffff::/48"), 56 | // Teredo 57 | new IPNetwork("2001::/40"), 58 | new IPNetwork("2001:0:a00::/40"), 59 | new IPNetwork("2001:0:7f00::/40"), 60 | new IPNetwork("2001:0:a9fe::/48"), 61 | new IPNetwork("2001:0:ac10::/44"), 62 | new IPNetwork("2001:0:c000::/56"), 63 | new IPNetwork("2001:0:c000:200::/56"), 64 | new IPNetwork("2001:0:c0a8::/48"), 65 | new IPNetwork("2001:0:c612::/47"), 66 | new IPNetwork("2001:0:c633:6400::/56"), 67 | new IPNetwork("2001:0:cb00:7100::/56"), 68 | new IPNetwork("2001:0:e000::/36"), 69 | new IPNetwork("2001:0:f000::/36"), 70 | new IPNetwork("2001:0:ffff:ffff::/64") 71 | }; 72 | 73 | internal static bool IsBogon(string ip) { 74 | for (int i = 0; i < s_bogonNetworks.Length; i++) 75 | { 76 | IPNetwork bogonNetwork = s_bogonNetworks[i]; 77 | if (bogonNetwork.Contains(ip)) 78 | { 79 | return true; 80 | } 81 | } 82 | return false; 83 | } 84 | 85 | private class IPNetwork 86 | { 87 | private int nMaskBits; 88 | private IPAddress netAddress; 89 | 90 | public IPNetwork(string ipAddress) 91 | { 92 | if (ipAddress.IndexOf('/') > 0) 93 | { 94 | string[] addressAndMask = ipAddress.Split('/'); 95 | ipAddress = addressAndMask[0]; 96 | nMaskBits = Int32.Parse(addressAndMask[1]); 97 | } 98 | else { 99 | nMaskBits = -1; 100 | } 101 | netAddress = IPAddress.Parse(ipAddress); 102 | } 103 | 104 | public bool Contains(string ipAddress) 105 | { 106 | try 107 | { 108 | return Contains(IPAddress.Parse(ipAddress)); 109 | } 110 | catch(Exception) 111 | { 112 | return false; 113 | } 114 | } 115 | 116 | public bool Contains(IPAddress ipAddress) 117 | { 118 | if (ipAddress == null || ipAddress.AddressFamily != netAddress.AddressFamily) 119 | { 120 | return false; 121 | } 122 | 123 | if (nMaskBits < 0) 124 | { 125 | return ipAddress.Equals(netAddress); 126 | } 127 | 128 | byte[] remAddr = ipAddress.GetAddressBytes(); 129 | byte[] reqAddr = netAddress.GetAddressBytes(); 130 | 131 | int nMaskFullBytes = nMaskBits / 8; 132 | byte finalByte = (byte) (0xFF00 >> (nMaskBits & 0x07)); 133 | 134 | for (int i = 0; i < nMaskFullBytes; i++) 135 | { 136 | if (remAddr[i] != reqAddr[i]) 137 | { 138 | return false; 139 | } 140 | } 141 | 142 | if (finalByte != 0) 143 | { 144 | return (remAddr[nMaskFullBytes] & finalByte) == (reqAddr[nMaskFullBytes] & finalByte); 145 | } 146 | 147 | return true; 148 | } 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/IPinfo/Utilities/CountryHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | using IPinfo.Models; 4 | 5 | namespace IPinfo.Utilities 6 | { 7 | /// 8 | /// CountryHelper class contains country parsing helper methods. 9 | /// 10 | internal static class CountryHelper 11 | { 12 | // Static dictionary for countries 13 | private static readonly Dictionary Countries = new Dictionary 14 | { 15 | {"BD", "Bangladesh"}, 16 | {"BE", "Belgium"}, 17 | {"BF", "Burkina Faso"}, 18 | {"BG", "Bulgaria"}, 19 | {"BA", "Bosnia and Herzegovina"}, 20 | {"BB", "Barbados"}, 21 | {"WF", "Wallis and Futuna"}, 22 | {"BL", "Saint Barthelemy"}, 23 | {"BM", "Bermuda"}, 24 | {"BN", "Brunei"}, 25 | {"BO", "Bolivia"}, 26 | {"BH", "Bahrain"}, 27 | {"BI", "Burundi"}, 28 | {"BJ", "Benin"}, 29 | {"BT", "Bhutan"}, 30 | {"JM", "Jamaica"}, 31 | {"BV", "Bouvet Island"}, 32 | {"BW", "Botswana"}, 33 | {"WS", "Samoa"}, 34 | {"BQ", "Bonaire, Saint Eustatius and Saba "}, 35 | {"BR", "Brazil"}, 36 | {"BS", "Bahamas"}, 37 | {"JE", "Jersey"}, 38 | {"BY", "Belarus"}, 39 | {"BZ", "Belize"}, 40 | {"RU", "Russia"}, 41 | {"RW", "Rwanda"}, 42 | {"RS", "Serbia"}, 43 | {"TL", "East Timor"}, 44 | {"RE", "Reunion"}, 45 | {"TM", "Turkmenistan"}, 46 | {"TJ", "Tajikistan"}, 47 | {"RO", "Romania"}, 48 | {"TK", "Tokelau"}, 49 | {"GW", "Guinea-Bissau"}, 50 | {"GU", "Guam"}, 51 | {"GT", "Guatemala"}, 52 | {"GS", "South Georgia and the South Sandwich Islands"}, 53 | {"GR", "Greece"}, 54 | {"GQ", "Equatorial Guinea"}, 55 | {"GP", "Guadeloupe"}, 56 | {"JP", "Japan"}, 57 | {"GY", "Guyana"}, 58 | {"GG", "Guernsey"}, 59 | {"GF", "French Guiana"}, 60 | {"GE", "Georgia"}, 61 | {"GD", "Grenada"}, 62 | {"GB", "United Kingdom"}, 63 | {"GA", "Gabon"}, 64 | {"SV", "El Salvador"}, 65 | {"GN", "Guinea"}, 66 | {"GM", "Gambia"}, 67 | {"GL", "Greenland"}, 68 | {"GI", "Gibraltar"}, 69 | {"GH", "Ghana"}, 70 | {"OM", "Oman"}, 71 | {"TN", "Tunisia"}, 72 | {"JO", "Jordan"}, 73 | {"HR", "Croatia"}, 74 | {"HT", "Haiti"}, 75 | {"HU", "Hungary"}, 76 | {"HK", "Hong Kong"}, 77 | {"HN", "Honduras"}, 78 | {"HM", "Heard Island and McDonald Islands"}, 79 | {"VE", "Venezuela"}, 80 | {"PR", "Puerto Rico"}, 81 | {"PS", "Palestinian Territory"}, 82 | {"PW", "Palau"}, 83 | {"PT", "Portugal"}, 84 | {"SJ", "Svalbard and Jan Mayen"}, 85 | {"PY", "Paraguay"}, 86 | {"IQ", "Iraq"}, 87 | {"PA", "Panama"}, 88 | {"PF", "French Polynesia"}, 89 | {"PG", "Papua New Guinea"}, 90 | {"PE", "Peru"}, 91 | {"PK", "Pakistan"}, 92 | {"PH", "Philippines"}, 93 | {"PN", "Pitcairn"}, 94 | {"PL", "Poland"}, 95 | {"PM", "Saint Pierre and Miquelon"}, 96 | {"ZM", "Zambia"}, 97 | {"EH", "Western Sahara"}, 98 | {"EE", "Estonia"}, 99 | {"EG", "Egypt"}, 100 | {"ZA", "South Africa"}, 101 | {"EC", "Ecuador"}, 102 | {"IT", "Italy"}, 103 | {"VN", "Vietnam"}, 104 | {"SB", "Solomon Islands"}, 105 | {"ET", "Ethiopia"}, 106 | {"SO", "Somalia"}, 107 | {"ZW", "Zimbabwe"}, 108 | {"SA", "Saudi Arabia"}, 109 | {"ES", "Spain"}, 110 | {"ER", "Eritrea"}, 111 | {"ME", "Montenegro"}, 112 | {"MD", "Moldova"}, 113 | {"MG", "Madagascar"}, 114 | {"MF", "Saint Martin"}, 115 | {"MA", "Morocco"}, 116 | {"MC", "Monaco"}, 117 | {"UZ", "Uzbekistan"}, 118 | {"MM", "Myanmar"}, 119 | {"ML", "Mali"}, 120 | {"MO", "Macao"}, 121 | {"MN", "Mongolia"}, 122 | {"MH", "Marshall Islands"}, 123 | {"MK", "Macedonia"}, 124 | {"MU", "Mauritius"}, 125 | {"MT", "Malta"}, 126 | {"MW", "Malawi"}, 127 | {"MV", "Maldives"}, 128 | {"MQ", "Martinique"}, 129 | {"MP", "Northern Mariana Islands"}, 130 | {"MS", "Montserrat"}, 131 | {"MR", "Mauritania"}, 132 | {"IM", "Isle of Man"}, 133 | {"UG", "Uganda"}, 134 | {"TZ", "Tanzania"}, 135 | {"MY", "Malaysia"}, 136 | {"MX", "Mexico"}, 137 | {"IL", "Israel"}, 138 | {"FR", "France"}, 139 | {"IO", "British Indian Ocean Territory"}, 140 | {"SH", "Saint Helena"}, 141 | {"FI", "Finland"}, 142 | {"FJ", "Fiji"}, 143 | {"FK", "Falkland Islands"}, 144 | {"FM", "Micronesia"}, 145 | {"FO", "Faroe Islands"}, 146 | {"NI", "Nicaragua"}, 147 | {"NL", "Netherlands"}, 148 | {"NO", "Norway"}, 149 | {"NA", "Namibia"}, 150 | {"VU", "Vanuatu"}, 151 | {"NC", "New Caledonia"}, 152 | {"NE", "Niger"}, 153 | {"NF", "Norfolk Island"}, 154 | {"NG", "Nigeria"}, 155 | {"NZ", "New Zealand"}, 156 | {"NP", "Nepal"}, 157 | {"NR", "Nauru"}, 158 | {"NU", "Niue"}, 159 | {"CK", "Cook Islands"}, 160 | {"XK", "Kosovo"}, 161 | {"CI", "Ivory Coast"}, 162 | {"CH", "Switzerland"}, 163 | {"CO", "Colombia"}, 164 | {"CN", "China"}, 165 | {"CM", "Cameroon"}, 166 | {"CL", "Chile"}, 167 | {"CC", "Cocos Islands"}, 168 | {"CA", "Canada"}, 169 | {"CG", "Republic of the Congo"}, 170 | {"CF", "Central African Republic"}, 171 | {"CD", "Democratic Republic of the Congo"}, 172 | {"CZ", "Czech Republic"}, 173 | {"CY", "Cyprus"}, 174 | {"CX", "Christmas Island"}, 175 | {"CR", "Costa Rica"}, 176 | {"CW", "Curacao"}, 177 | {"CV", "Cape Verde"}, 178 | {"CU", "Cuba"}, 179 | {"SZ", "Swaziland"}, 180 | {"SY", "Syria"}, 181 | {"SX", "Sint Maarten"}, 182 | {"KG", "Kyrgyzstan"}, 183 | {"KE", "Kenya"}, 184 | {"SS", "South Sudan"}, 185 | {"SR", "Suriname"}, 186 | {"KI", "Kiribati"}, 187 | {"KH", "Cambodia"}, 188 | {"KN", "Saint Kitts and Nevis"}, 189 | {"KM", "Comoros"}, 190 | {"ST", "Sao Tome and Principe"}, 191 | {"SK", "Slovakia"}, 192 | {"KR", "South Korea"}, 193 | {"SI", "Slovenia"}, 194 | {"KP", "North Korea"}, 195 | {"KW", "Kuwait"}, 196 | {"SN", "Senegal"}, 197 | {"SM", "San Marino"}, 198 | {"SL", "Sierra Leone"}, 199 | {"SC", "Seychelles"}, 200 | {"KZ", "Kazakhstan"}, 201 | {"KY", "Cayman Islands"}, 202 | {"SG", "Singapore"}, 203 | {"SE", "Sweden"}, 204 | {"SD", "Sudan"}, 205 | {"DO", "Dominican Republic"}, 206 | {"DM", "Dominica"}, 207 | {"DJ", "Djibouti"}, 208 | {"DK", "Denmark"}, 209 | {"VG", "British Virgin Islands"}, 210 | {"DE", "Germany"}, 211 | {"YE", "Yemen"}, 212 | {"DZ", "Algeria"}, 213 | {"US", "United States"}, 214 | {"UY", "Uruguay"}, 215 | {"YT", "Mayotte"}, 216 | {"UM", "United States Minor Outlying Islands"}, 217 | {"LB", "Lebanon"}, 218 | {"LC", "Saint Lucia"}, 219 | {"LA", "Laos"}, 220 | {"TV", "Tuvalu"}, 221 | {"TW", "Taiwan"}, 222 | {"TT", "Trinidad and Tobago"}, 223 | {"TR", "Turkey"}, 224 | {"LK", "Sri Lanka"}, 225 | {"LI", "Liechtenstein"}, 226 | {"LV", "Latvia"}, 227 | {"TO", "Tonga"}, 228 | {"LT", "Lithuania"}, 229 | {"LU", "Luxembourg"}, 230 | {"LR", "Liberia"}, 231 | {"LS", "Lesotho"}, 232 | {"TH", "Thailand"}, 233 | {"TF", "French Southern Territories"}, 234 | {"TG", "Togo"}, 235 | {"TD", "Chad"}, 236 | {"TC", "Turks and Caicos Islands"}, 237 | {"LY", "Libya"}, 238 | {"VA", "Vatican"}, 239 | {"VC", "Saint Vincent and the Grenadines"}, 240 | {"AE", "United Arab Emirates"}, 241 | {"AD", "Andorra"}, 242 | {"AG", "Antigua and Barbuda"}, 243 | {"AF", "Afghanistan"}, 244 | {"AI", "Anguilla"}, 245 | {"VI", "U.S. Virgin Islands"}, 246 | {"IS", "Iceland"}, 247 | {"IR", "Iran"}, 248 | {"AM", "Armenia"}, 249 | {"AL", "Albania"}, 250 | {"AO", "Angola"}, 251 | {"AQ", "Antarctica"}, 252 | {"AS", "American Samoa"}, 253 | {"AR", "Argentina"}, 254 | {"AU", "Australia"}, 255 | {"AT", "Austria"}, 256 | {"AW", "Aruba"}, 257 | {"IN", "India"}, 258 | {"AX", "Aland Islands"}, 259 | {"AZ", "Azerbaijan"}, 260 | {"IE", "Ireland"}, 261 | {"ID", "Indonesia"}, 262 | {"UA", "Ukraine"}, 263 | {"QA", "Qatar"}, 264 | {"MZ", "Mozambique"} 265 | }; 266 | 267 | // Static dictionary for country flags 268 | private static readonly Dictionary CountriesFlags = new Dictionary 269 | { 270 | {"AD", new CountryFlag { Emoji = "🇦🇩", Unicode = "U+1F1E6 U+1F1E9" }}, 271 | {"AE", new CountryFlag { Emoji = "🇦🇪", Unicode = "U+1F1E6 U+1F1EA" }}, 272 | {"AF", new CountryFlag { Emoji = "🇦🇫", Unicode = "U+1F1E6 U+1F1EB" }}, 273 | {"AG", new CountryFlag { Emoji = "🇦🇬", Unicode = "U+1F1E6 U+1F1EC" }}, 274 | {"AI", new CountryFlag { Emoji = "🇦🇮", Unicode = "U+1F1E6 U+1F1EE" }}, 275 | {"AL", new CountryFlag { Emoji = "🇦🇱", Unicode = "U+1F1E6 U+1F1F1" }}, 276 | {"AM", new CountryFlag { Emoji = "🇦🇲", Unicode = "U+1F1E6 U+1F1F2" }}, 277 | {"AO", new CountryFlag { Emoji = "🇦🇴", Unicode = "U+1F1E6 U+1F1F4" }}, 278 | {"AQ", new CountryFlag { Emoji = "🇦🇶", Unicode = "U+1F1E6 U+1F1F6" }}, 279 | {"AR", new CountryFlag { Emoji = "🇦🇷", Unicode = "U+1F1E6 U+1F1F7" }}, 280 | {"AS", new CountryFlag { Emoji = "🇦🇸", Unicode = "U+1F1E6 U+1F1F8" }}, 281 | {"AT", new CountryFlag { Emoji = "🇦🇹", Unicode = "U+1F1E6 U+1F1F9" }}, 282 | {"AU", new CountryFlag { Emoji = "🇦🇺", Unicode = "U+1F1E6 U+1F1FA" }}, 283 | {"AW", new CountryFlag { Emoji = "🇦🇼", Unicode = "U+1F1E6 U+1F1FC" }}, 284 | {"AX", new CountryFlag { Emoji = "🇦🇽", Unicode = "U+1F1E6 U+1F1FD" }}, 285 | {"AZ", new CountryFlag { Emoji = "🇦🇿", Unicode = "U+1F1E6 U+1F1FF" }}, 286 | {"BA", new CountryFlag { Emoji = "🇧🇦", Unicode = "U+1F1E7 U+1F1E6" }}, 287 | {"BB", new CountryFlag { Emoji = "🇧🇧", Unicode = "U+1F1E7 U+1F1E7" }}, 288 | {"BD", new CountryFlag { Emoji = "🇧🇩", Unicode = "U+1F1E7 U+1F1E9" }}, 289 | {"BE", new CountryFlag { Emoji = "🇧🇪", Unicode = "U+1F1E7 U+1F1EA" }}, 290 | {"BF", new CountryFlag { Emoji = "🇧🇫", Unicode = "U+1F1E7 U+1F1EB" }}, 291 | {"BG", new CountryFlag { Emoji = "🇧🇬", Unicode = "U+1F1E7 U+1F1EC" }}, 292 | {"BH", new CountryFlag { Emoji = "🇧🇭", Unicode = "U+1F1E7 U+1F1ED" }}, 293 | {"BI", new CountryFlag { Emoji = "🇧🇮", Unicode = "U+1F1E7 U+1F1EE" }}, 294 | {"BJ", new CountryFlag { Emoji = "🇧🇯", Unicode = "U+1F1E7 U+1F1EF" }}, 295 | {"BL", new CountryFlag { Emoji = "🇧🇱", Unicode = "U+1F1E7 U+1F1F1" }}, 296 | {"BM", new CountryFlag { Emoji = "🇧🇲", Unicode = "U+1F1E7 U+1F1F2" }}, 297 | {"BN", new CountryFlag { Emoji = "🇧🇳", Unicode = "U+1F1E7 U+1F1F3" }}, 298 | {"BO", new CountryFlag { Emoji = "🇧🇴", Unicode = "U+1F1E7 U+1F1F4" }}, 299 | {"BQ", new CountryFlag { Emoji = "🇧🇶", Unicode = "U+1F1E7 U+1F1F6" }}, 300 | {"BR", new CountryFlag { Emoji = "🇧🇷", Unicode = "U+1F1E7 U+1F1F7" }}, 301 | {"BS", new CountryFlag { Emoji = "🇧🇸", Unicode = "U+1F1E7 U+1F1F8" }}, 302 | {"BT", new CountryFlag { Emoji = "🇧🇹", Unicode = "U+1F1E7 U+1F1F9" }}, 303 | {"BV", new CountryFlag { Emoji = "🇧🇻", Unicode = "U+1F1E7 U+1F1FB" }}, 304 | {"BW", new CountryFlag { Emoji = "🇧🇼", Unicode = "U+1F1E7 U+1F1FC" }}, 305 | {"BY", new CountryFlag { Emoji = "🇧🇾", Unicode = "U+1F1E7 U+1F1FE" }}, 306 | {"BZ", new CountryFlag { Emoji = "🇧🇿", Unicode = "U+1F1E7 U+1F1FF" }}, 307 | {"CA", new CountryFlag { Emoji = "🇨🇦", Unicode = "U+1F1E8 U+1F1E6" }}, 308 | {"CC", new CountryFlag { Emoji = "🇨🇨", Unicode = "U+1F1E8 U+1F1E8" }}, 309 | {"CD", new CountryFlag { Emoji = "🇨🇩", Unicode = "U+1F1E8 U+1F1E9" }}, 310 | {"CF", new CountryFlag { Emoji = "🇨🇫", Unicode = "U+1F1E8 U+1F1EB" }}, 311 | {"CG", new CountryFlag { Emoji = "🇨🇬", Unicode = "U+1F1E8 U+1F1EC" }}, 312 | {"CH", new CountryFlag { Emoji = "🇨🇭", Unicode = "U+1F1E8 U+1F1ED" }}, 313 | {"CI", new CountryFlag { Emoji = "🇨🇮", Unicode = "U+1F1E8 U+1F1EE" }}, 314 | {"CK", new CountryFlag { Emoji = "🇨🇰", Unicode = "U+1F1E8 U+1F1F0" }}, 315 | {"CL", new CountryFlag { Emoji = "🇨🇱", Unicode = "U+1F1E8 U+1F1F1" }}, 316 | {"CM", new CountryFlag { Emoji = "🇨🇲", Unicode = "U+1F1E8 U+1F1F2" }}, 317 | {"CN", new CountryFlag { Emoji = "🇨🇳", Unicode = "U+1F1E8 U+1F1F3" }}, 318 | {"CO", new CountryFlag { Emoji = "🇨🇴", Unicode = "U+1F1E8 U+1F1F4" }}, 319 | {"CR", new CountryFlag { Emoji = "🇨🇷", Unicode = "U+1F1E8 U+1F1F7" }}, 320 | {"CU", new CountryFlag { Emoji = "🇨🇺", Unicode = "U+1F1E8 U+1F1FA" }}, 321 | {"CV", new CountryFlag { Emoji = "🇨🇻", Unicode = "U+1F1E8 U+1F1FB" }}, 322 | {"CW", new CountryFlag { Emoji = "🇨🇼", Unicode = "U+1F1E8 U+1F1FC" }}, 323 | {"CX", new CountryFlag { Emoji = "🇨🇽", Unicode = "U+1F1E8 U+1F1FD" }}, 324 | {"CY", new CountryFlag { Emoji = "🇨🇾", Unicode = "U+1F1E8 U+1F1FE" }}, 325 | {"CZ", new CountryFlag { Emoji = "🇨🇿", Unicode = "U+1F1E8 U+1F1FF" }}, 326 | {"DE", new CountryFlag { Emoji = "🇩🇪", Unicode = "U+1F1E9 U+1F1EA" }}, 327 | {"DJ", new CountryFlag { Emoji = "🇩🇯", Unicode = "U+1F1E9 U+1F1EF" }}, 328 | {"DK", new CountryFlag { Emoji = "🇩🇰", Unicode = "U+1F1E9 U+1F1F0" }}, 329 | {"DM", new CountryFlag { Emoji = "🇩🇲", Unicode = "U+1F1E9 U+1F1F2" }}, 330 | {"DO", new CountryFlag { Emoji = "🇩🇴", Unicode = "U+1F1E9 U+1F1F4" }}, 331 | {"DZ", new CountryFlag { Emoji = "🇩🇿", Unicode = "U+1F1E9 U+1F1FF" }}, 332 | {"EC", new CountryFlag { Emoji = "🇪🇨", Unicode = "U+1F1EA U+1F1E8" }}, 333 | {"EE", new CountryFlag { Emoji = "🇪🇪", Unicode = "U+1F1EA U+1F1EA" }}, 334 | {"EG", new CountryFlag { Emoji = "🇪🇬", Unicode = "U+1F1EA U+1F1EC" }}, 335 | {"EH", new CountryFlag { Emoji = "🇪🇭", Unicode = "U+1F1EA U+1F1ED" }}, 336 | {"ER", new CountryFlag { Emoji = "🇪🇷", Unicode = "U+1F1EA U+1F1F7" }}, 337 | {"ES", new CountryFlag { Emoji = "🇪🇸", Unicode = "U+1F1EA U+1F1F8" }}, 338 | {"ET", new CountryFlag { Emoji = "🇪🇹", Unicode = "U+1F1EA U+1F1F9" }}, 339 | {"FI", new CountryFlag { Emoji = "🇫🇮", Unicode = "U+1F1EB U+1F1EE" }}, 340 | {"FJ", new CountryFlag { Emoji = "🇫🇯", Unicode = "U+1F1EB U+1F1EF" }}, 341 | {"FK", new CountryFlag { Emoji = "🇫🇰", Unicode = "U+1F1EB U+1F1F0" }}, 342 | {"FM", new CountryFlag { Emoji = "🇫🇲", Unicode = "U+1F1EB U+1F1F2" }}, 343 | {"FO", new CountryFlag { Emoji = "🇫🇴", Unicode = "U+1F1EB U+1F1F4" }}, 344 | {"FR", new CountryFlag { Emoji = "🇫🇷", Unicode = "U+1F1EB U+1F1F7" }}, 345 | {"GA", new CountryFlag { Emoji = "🇬🇦", Unicode = "U+1F1EC U+1F1E6" }}, 346 | {"GB", new CountryFlag { Emoji = "🇬🇧", Unicode = "U+1F1EC U+1F1E7" }}, 347 | {"GD", new CountryFlag { Emoji = "🇬🇩", Unicode = "U+1F1EC U+1F1E9" }}, 348 | {"GE", new CountryFlag { Emoji = "🇬🇪", Unicode = "U+1F1EC U+1F1EA" }}, 349 | {"GF", new CountryFlag { Emoji = "🇬🇫", Unicode = "U+1F1EC U+1F1EB" }}, 350 | {"GG", new CountryFlag { Emoji = "🇬🇬", Unicode = "U+1F1EC U+1F1EC" }}, 351 | {"GH", new CountryFlag { Emoji = "🇬🇭", Unicode = "U+1F1EC U+1F1ED" }}, 352 | {"GI", new CountryFlag { Emoji = "🇬🇮", Unicode = "U+1F1EC U+1F1EE" }}, 353 | {"GL", new CountryFlag { Emoji = "🇬🇱", Unicode = "U+1F1EC U+1F1F1" }}, 354 | {"GM", new CountryFlag { Emoji = "🇬🇲", Unicode = "U+1F1EC U+1F1F2" }}, 355 | {"GN", new CountryFlag { Emoji = "🇬🇳", Unicode = "U+1F1EC U+1F1F3" }}, 356 | {"GP", new CountryFlag { Emoji = "🇬🇵", Unicode = "U+1F1EC U+1F1F5" }}, 357 | {"GQ", new CountryFlag { Emoji = "🇬🇶", Unicode = "U+1F1EC U+1F1F6" }}, 358 | {"GR", new CountryFlag { Emoji = "🇬🇷", Unicode = "U+1F1EC U+1F1F7" }}, 359 | {"GS", new CountryFlag { Emoji = "🇬🇸", Unicode = "U+1F1EC U+1F1F8" }}, 360 | {"GT", new CountryFlag { Emoji = "🇬🇹", Unicode = "U+1F1EC U+1F1F9" }}, 361 | {"GU", new CountryFlag { Emoji = "🇬🇺", Unicode = "U+1F1EC U+1F1FA" }}, 362 | {"GW", new CountryFlag { Emoji = "🇬🇼", Unicode = "U+1F1EC U+1F1FC" }}, 363 | {"GY", new CountryFlag { Emoji = "🇬🇾", Unicode = "U+1F1EC U+1F1FE" }}, 364 | {"HK", new CountryFlag { Emoji = "🇭🇰", Unicode = "U+1F1ED U+1F1F0" }}, 365 | {"HM", new CountryFlag { Emoji = "🇭🇲", Unicode = "U+1F1ED U+1F1F2" }}, 366 | {"HN", new CountryFlag { Emoji = "🇭🇳", Unicode = "U+1F1ED U+1F1F3" }}, 367 | {"HR", new CountryFlag { Emoji = "🇭🇷", Unicode = "U+1F1ED U+1F1F7" }}, 368 | {"HT", new CountryFlag { Emoji = "🇭🇹", Unicode = "U+1F1ED U+1F1F9" }}, 369 | {"HU", new CountryFlag { Emoji = "🇭🇺", Unicode = "U+1F1ED U+1F1FA" }}, 370 | {"ID", new CountryFlag { Emoji = "🇮🇩", Unicode = "U+1F1EE U+1F1E9" }}, 371 | {"IE", new CountryFlag { Emoji = "🇮🇪", Unicode = "U+1F1EE U+1F1EA" }}, 372 | {"IL", new CountryFlag { Emoji = "🇮🇱", Unicode = "U+1F1EE U+1F1F1" }}, 373 | {"IM", new CountryFlag { Emoji = "🇮🇲", Unicode = "U+1F1EE U+1F1F2" }}, 374 | {"IN", new CountryFlag { Emoji = "🇮🇳", Unicode = "U+1F1EE U+1F1F3" }}, 375 | {"IO", new CountryFlag { Emoji = "🇮🇴", Unicode = "U+1F1EE U+1F1F4" }}, 376 | {"IQ", new CountryFlag { Emoji = "🇮🇶", Unicode = "U+1F1EE U+1F1F6" }}, 377 | {"IR", new CountryFlag { Emoji = "🇮🇷", Unicode = "U+1F1EE U+1F1F7" }}, 378 | {"IS", new CountryFlag { Emoji = "🇮🇸", Unicode = "U+1F1EE U+1F1F8" }}, 379 | {"IT", new CountryFlag { Emoji = "🇮🇹", Unicode = "U+1F1EE U+1F1F9" }}, 380 | {"JE", new CountryFlag { Emoji = "🇯🇪", Unicode = "U+1F1EF U+1F1EA" }}, 381 | {"JM", new CountryFlag { Emoji = "🇯🇲", Unicode = "U+1F1EF U+1F1F2" }}, 382 | {"JO", new CountryFlag { Emoji = "🇯🇴", Unicode = "U+1F1EF U+1F1F4" }}, 383 | {"JP", new CountryFlag { Emoji = "🇯🇵", Unicode = "U+1F1EF U+1F1F5" }}, 384 | {"KE", new CountryFlag { Emoji = "🇰🇪", Unicode = "U+1F1F0 U+1F1EA" }}, 385 | {"KG", new CountryFlag { Emoji = "🇰🇬", Unicode = "U+1F1F0 U+1F1EC" }}, 386 | {"KH", new CountryFlag { Emoji = "🇰🇭", Unicode = "U+1F1F0 U+1F1ED" }}, 387 | {"KI", new CountryFlag { Emoji = "🇰🇮", Unicode = "U+1F1F0 U+1F1EE" }}, 388 | {"KM", new CountryFlag { Emoji = "🇰🇲", Unicode = "U+1F1F0 U+1F1F2" }}, 389 | {"KN", new CountryFlag { Emoji = "🇰🇳", Unicode = "U+1F1F0 U+1F1F3" }}, 390 | {"KP", new CountryFlag { Emoji = "🇰🇵", Unicode = "U+1F1F0 U+1F1F5" }}, 391 | {"KR", new CountryFlag { Emoji = "🇰🇷", Unicode = "U+1F1F0 U+1F1F7" }}, 392 | {"KW", new CountryFlag { Emoji = "🇰🇼", Unicode = "U+1F1F0 U+1F1FC" }}, 393 | {"KY", new CountryFlag { Emoji = "🇰🇾", Unicode = "U+1F1F0 U+1F1FE" }}, 394 | {"KZ", new CountryFlag { Emoji = "🇰🇿", Unicode = "U+1F1F0 U+1F1FF" }}, 395 | {"LA", new CountryFlag { Emoji = "🇱🇦", Unicode = "U+1F1F1 U+1F1E6" }}, 396 | {"LB", new CountryFlag { Emoji = "🇱🇧", Unicode = "U+1F1F1 U+1F1E7" }}, 397 | {"LC", new CountryFlag { Emoji = "🇱🇨", Unicode = "U+1F1F1 U+1F1E8" }}, 398 | {"LI", new CountryFlag { Emoji = "🇱🇮", Unicode = "U+1F1F1 U+1F1EE" }}, 399 | {"LK", new CountryFlag { Emoji = "🇱🇰", Unicode = "U+1F1F1 U+1F1F0" }}, 400 | {"LR", new CountryFlag { Emoji = "🇱🇷", Unicode = "U+1F1F1 U+1F1F7" }}, 401 | {"LS", new CountryFlag { Emoji = "🇱🇸", Unicode = "U+1F1F1 U+1F1F8" }}, 402 | {"LT", new CountryFlag { Emoji = "🇱🇹", Unicode = "U+1F1F1 U+1F1F9" }}, 403 | {"LU", new CountryFlag { Emoji = "🇱🇺", Unicode = "U+1F1F1 U+1F1FA" }}, 404 | {"LV", new CountryFlag { Emoji = "🇱🇻", Unicode = "U+1F1F1 U+1F1FB" }}, 405 | {"LY", new CountryFlag { Emoji = "🇱🇾", Unicode = "U+1F1F1 U+1F1FE" }}, 406 | {"MA", new CountryFlag { Emoji = "🇲🇦", Unicode = "U+1F1F2 U+1F1E6" }}, 407 | {"MC", new CountryFlag { Emoji = "🇲🇨", Unicode = "U+1F1F2 U+1F1E8" }}, 408 | {"MD", new CountryFlag { Emoji = "🇲🇩", Unicode = "U+1F1F2 U+1F1E9" }}, 409 | {"ME", new CountryFlag { Emoji = "🇲🇪", Unicode = "U+1F1F2 U+1F1EA" }}, 410 | {"MF", new CountryFlag { Emoji = "🇲🇫", Unicode = "U+1F1F2 U+1F1EB" }}, 411 | {"MG", new CountryFlag { Emoji = "🇲🇬", Unicode = "U+1F1F2 U+1F1EC" }}, 412 | {"MH", new CountryFlag { Emoji = "🇲🇭", Unicode = "U+1F1F2 U+1F1ED" }}, 413 | {"MK", new CountryFlag { Emoji = "🇲🇰", Unicode = "U+1F1F2 U+1F1F0" }}, 414 | {"ML", new CountryFlag { Emoji = "🇲🇱", Unicode = "U+1F1F2 U+1F1F1" }}, 415 | {"MM", new CountryFlag { Emoji = "🇲🇲", Unicode = "U+1F1F2 U+1F1F2" }}, 416 | {"MN", new CountryFlag { Emoji = "🇲🇳", Unicode = "U+1F1F2 U+1F1F3" }}, 417 | {"MO", new CountryFlag { Emoji = "🇲🇴", Unicode = "U+1F1F2 U+1F1F4" }}, 418 | {"MP", new CountryFlag { Emoji = "🇲🇵", Unicode = "U+1F1F2 U+1F1F5" }}, 419 | {"MQ", new CountryFlag { Emoji = "🇲🇶", Unicode = "U+1F1F2 U+1F1F6" }}, 420 | {"MR", new CountryFlag { Emoji = "🇲🇷", Unicode = "U+1F1F2 U+1F1F7" }}, 421 | {"MS", new CountryFlag { Emoji = "🇲🇸", Unicode = "U+1F1F2 U+1F1F8" }}, 422 | {"MT", new CountryFlag { Emoji = "🇲🇹", Unicode = "U+1F1F2 U+1F1F9" }}, 423 | {"MU", new CountryFlag { Emoji = "🇲🇺", Unicode = "U+1F1F2 U+1F1FA" }}, 424 | {"MV", new CountryFlag { Emoji = "🇲🇻", Unicode = "U+1F1F2 U+1F1FB" }}, 425 | {"MW", new CountryFlag { Emoji = "🇲🇼", Unicode = "U+1F1F2 U+1F1FC" }}, 426 | {"MX", new CountryFlag { Emoji = "🇲🇽", Unicode = "U+1F1F2 U+1F1FD" }}, 427 | {"MY", new CountryFlag { Emoji = "🇲🇾", Unicode = "U+1F1F2 U+1F1FE" }}, 428 | {"MZ", new CountryFlag { Emoji = "🇲🇿", Unicode = "U+1F1F2 U+1F1FF" }}, 429 | {"NA", new CountryFlag { Emoji = "🇳🇦", Unicode = "U+1F1F3 U+1F1E6" }}, 430 | {"NC", new CountryFlag { Emoji = "🇳🇨", Unicode = "U+1F1F3 U+1F1E8" }}, 431 | {"NE", new CountryFlag { Emoji = "🇳🇪", Unicode = "U+1F1F3 U+1F1EA" }}, 432 | {"NF", new CountryFlag { Emoji = "🇳🇫", Unicode = "U+1F1F3 U+1F1EB" }}, 433 | {"NG", new CountryFlag { Emoji = "🇳🇬", Unicode = "U+1F1F3 U+1F1EC" }}, 434 | {"NI", new CountryFlag { Emoji = "🇳🇮", Unicode = "U+1F1F3 U+1F1EE" }}, 435 | {"NL", new CountryFlag { Emoji = "🇳🇱", Unicode = "U+1F1F3 U+1F1F1" }}, 436 | {"NO", new CountryFlag { Emoji = "🇳🇴", Unicode = "U+1F1F3 U+1F1F4" }}, 437 | {"NP", new CountryFlag { Emoji = "🇳🇵", Unicode = "U+1F1F3 U+1F1F5" }}, 438 | {"NR", new CountryFlag { Emoji = "🇳🇷", Unicode = "U+1F1F3 U+1F1F7" }}, 439 | {"NU", new CountryFlag { Emoji = "🇳🇺", Unicode = "U+1F1F3 U+1F1FA" }}, 440 | {"NZ", new CountryFlag { Emoji = "🇳🇿", Unicode = "U+1F1F3 U+1F1FF" }}, 441 | {"OM", new CountryFlag { Emoji = "🇴🇲", Unicode = "U+1F1F4 U+1F1F2" }}, 442 | {"PA", new CountryFlag { Emoji = "🇵🇦", Unicode = "U+1F1F5 U+1F1E6" }}, 443 | {"PE", new CountryFlag { Emoji = "🇵🇪", Unicode = "U+1F1F5 U+1F1EA" }}, 444 | {"PF", new CountryFlag { Emoji = "🇵🇫", Unicode = "U+1F1F5 U+1F1EB" }}, 445 | {"PG", new CountryFlag { Emoji = "🇵🇬", Unicode = "U+1F1F5 U+1F1EC" }}, 446 | {"PH", new CountryFlag { Emoji = "🇵🇭", Unicode = "U+1F1F5 U+1F1ED" }}, 447 | {"PK", new CountryFlag { Emoji = "🇵🇰", Unicode = "U+1F1F5 U+1F1F0" }}, 448 | {"PL", new CountryFlag { Emoji = "🇵🇱", Unicode = "U+1F1F5 U+1F1F1" }}, 449 | {"PM", new CountryFlag { Emoji = "🇵🇲", Unicode = "U+1F1F5 U+1F1F2" }}, 450 | {"PN", new CountryFlag { Emoji = "🇵🇳", Unicode = "U+1F1F5 U+1F1F3" }}, 451 | {"PR", new CountryFlag { Emoji = "🇵🇷", Unicode = "U+1F1F5 U+1F1F7" }}, 452 | {"PS", new CountryFlag { Emoji = "🇵🇸", Unicode = "U+1F1F5 U+1F1F8" }}, 453 | {"PT", new CountryFlag { Emoji = "🇵🇹", Unicode = "U+1F1F5 U+1F1F9" }}, 454 | {"PW", new CountryFlag { Emoji = "🇵🇼", Unicode = "U+1F1F5 U+1F1FC" }}, 455 | {"PY", new CountryFlag { Emoji = "🇵🇾", Unicode = "U+1F1F5 U+1F1FE" }}, 456 | {"QA", new CountryFlag { Emoji = "🇶🇦", Unicode = "U+1F1F6 U+1F1E6" }}, 457 | {"RE", new CountryFlag { Emoji = "🇷🇪", Unicode = "U+1F1F7 U+1F1EA" }}, 458 | {"RO", new CountryFlag { Emoji = "🇷🇴", Unicode = "U+1F1F7 U+1F1F4" }}, 459 | {"RS", new CountryFlag { Emoji = "🇷🇸", Unicode = "U+1F1F7 U+1F1F8" }}, 460 | {"RU", new CountryFlag { Emoji = "🇷🇺", Unicode = "U+1F1F7 U+1F1FA" }}, 461 | {"RW", new CountryFlag { Emoji = "🇷🇼", Unicode = "U+1F1F7 U+1F1FC" }}, 462 | {"SA", new CountryFlag { Emoji = "🇸🇦", Unicode = "U+1F1F8 U+1F1E6" }}, 463 | {"SB", new CountryFlag { Emoji = "🇸🇧", Unicode = "U+1F1F8 U+1F1E7" }}, 464 | {"SC", new CountryFlag { Emoji = "🇸🇨", Unicode = "U+1F1F8 U+1F1E8" }}, 465 | {"SD", new CountryFlag { Emoji = "🇸🇩", Unicode = "U+1F1F8 U+1F1E9" }}, 466 | {"SE", new CountryFlag { Emoji = "🇸🇪", Unicode = "U+1F1F8 U+1F1EA" }}, 467 | {"SG", new CountryFlag { Emoji = "🇸🇬", Unicode = "U+1F1F8 U+1F1EC" }}, 468 | {"SH", new CountryFlag { Emoji = "🇸🇭", Unicode = "U+1F1F8 U+1F1ED" }}, 469 | {"SI", new CountryFlag { Emoji = "🇸🇮", Unicode = "U+1F1F8 U+1F1EE" }}, 470 | {"SJ", new CountryFlag { Emoji = "🇸🇯", Unicode = "U+1F1F8 U+1F1EF" }}, 471 | {"SK", new CountryFlag { Emoji = "🇸🇰", Unicode = "U+1F1F8 U+1F1F0" }}, 472 | {"SL", new CountryFlag { Emoji = "🇸🇱", Unicode = "U+1F1F8 U+1F1F1" }}, 473 | {"SM", new CountryFlag { Emoji = "🇸🇲", Unicode = "U+1F1F8 U+1F1F2" }}, 474 | {"SN", new CountryFlag { Emoji = "🇸🇳", Unicode = "U+1F1F8 U+1F1F3" }}, 475 | {"SO", new CountryFlag { Emoji = "🇸🇴", Unicode = "U+1F1F8 U+1F1F4" }}, 476 | {"SR", new CountryFlag { Emoji = "🇸🇷", Unicode = "U+1F1F8 U+1F1F7" }}, 477 | {"SS", new CountryFlag { Emoji = "🇸🇸", Unicode = "U+1F1F8 U+1F1F8" }}, 478 | {"ST", new CountryFlag { Emoji = "🇸🇹", Unicode = "U+1F1F8 U+1F1F9" }}, 479 | {"SV", new CountryFlag { Emoji = "🇸🇻", Unicode = "U+1F1F8 U+1F1FB" }}, 480 | {"SX", new CountryFlag { Emoji = "🇸🇽", Unicode = "U+1F1F8 U+1F1FD" }}, 481 | {"SY", new CountryFlag { Emoji = "🇸🇾", Unicode = "U+1F1F8 U+1F1FE" }}, 482 | {"SZ", new CountryFlag { Emoji = "🇸🇿", Unicode = "U+1F1F8 U+1F1FF" }}, 483 | {"TC", new CountryFlag { Emoji = "🇹🇨", Unicode = "U+1F1F9 U+1F1E8" }}, 484 | {"TD", new CountryFlag { Emoji = "🇹🇩", Unicode = "U+1F1F9 U+1F1E9" }}, 485 | {"TF", new CountryFlag { Emoji = "🇹🇫", Unicode = "U+1F1F9 U+1F1EB" }}, 486 | {"TG", new CountryFlag { Emoji = "🇹🇬", Unicode = "U+1F1F9 U+1F1EC" }}, 487 | {"TH", new CountryFlag { Emoji = "🇹🇭", Unicode = "U+1F1F9 U+1F1ED" }}, 488 | {"TJ", new CountryFlag { Emoji = "🇹🇯", Unicode = "U+1F1F9 U+1F1EF" }}, 489 | {"TK", new CountryFlag { Emoji = "🇹🇰", Unicode = "U+1F1F9 U+1F1F0" }}, 490 | {"TL", new CountryFlag { Emoji = "🇹🇱", Unicode = "U+1F1F9 U+1F1F1" }}, 491 | {"TM", new CountryFlag { Emoji = "🇹🇲", Unicode = "U+1F1F9 U+1F1F2" }}, 492 | {"TN", new CountryFlag { Emoji = "🇹🇳", Unicode = "U+1F1F9 U+1F1F3" }}, 493 | {"TO", new CountryFlag { Emoji = "🇹🇴", Unicode = "U+1F1F9 U+1F1F4" }}, 494 | {"TR", new CountryFlag { Emoji = "🇹🇷", Unicode = "U+1F1F9 U+1F1F7" }}, 495 | {"TT", new CountryFlag { Emoji = "🇹🇹", Unicode = "U+1F1F9 U+1F1F9" }}, 496 | {"TV", new CountryFlag { Emoji = "🇹🇻", Unicode = "U+1F1F9 U+1F1FB" }}, 497 | {"TW", new CountryFlag { Emoji = "🇹🇼", Unicode = "U+1F1F9 U+1F1FC" }}, 498 | {"TZ", new CountryFlag { Emoji = "🇹🇿", Unicode = "U+1F1F9 U+1F1FF" }}, 499 | {"UA", new CountryFlag { Emoji = "🇺🇦", Unicode = "U+1F1FA U+1F1E6" }}, 500 | {"UG", new CountryFlag { Emoji = "🇺🇬", Unicode = "U+1F1FA U+1F1EC" }}, 501 | {"UM", new CountryFlag { Emoji = "🇺🇲", Unicode = "U+1F1FA U+1F1F2" }}, 502 | {"US", new CountryFlag { Emoji = "🇺🇸", Unicode = "U+1F1FA U+1F1F8" }}, 503 | {"UY", new CountryFlag { Emoji = "🇺🇾", Unicode = "U+1F1FA U+1F1FE" }}, 504 | {"UZ", new CountryFlag { Emoji = "🇺🇿", Unicode = "U+1F1FA U+1F1FF" }}, 505 | {"VA", new CountryFlag { Emoji = "🇻🇦", Unicode = "U+1F1FB U+1F1E6" }}, 506 | {"VC", new CountryFlag { Emoji = "🇻🇨", Unicode = "U+1F1FB U+1F1E8" }}, 507 | {"VE", new CountryFlag { Emoji = "🇻🇪", Unicode = "U+1F1FB U+1F1EA" }}, 508 | {"VG", new CountryFlag { Emoji = "🇻🇬", Unicode = "U+1F1FB U+1F1EC" }}, 509 | {"VI", new CountryFlag { Emoji = "🇻🇮", Unicode = "U+1F1FB U+1F1EE" }}, 510 | {"VN", new CountryFlag { Emoji = "🇻🇳", Unicode = "U+1F1FB U+1F1F3" }}, 511 | {"VU", new CountryFlag { Emoji = "🇻🇺", Unicode = "U+1F1FB U+1F1FA" }}, 512 | {"WF", new CountryFlag { Emoji = "🇼🇫", Unicode = "U+1F1FC U+1F1EB" }}, 513 | {"WS", new CountryFlag { Emoji = "🇼🇸", Unicode = "U+1F1FC U+1F1F8" }}, 514 | {"XK", new CountryFlag { Emoji = "🇽🇰", Unicode = "U+1F1FD U+1F1F0" }}, 515 | {"YE", new CountryFlag { Emoji = "🇾🇪", Unicode = "U+1F1FE U+1F1EA" }}, 516 | {"YT", new CountryFlag { Emoji = "🇾🇹", Unicode = "U+1F1FE U+1F1F9" }}, 517 | {"ZA", new CountryFlag { Emoji = "🇿🇦", Unicode = "U+1F1FF U+1F1E6" }}, 518 | {"ZM", new CountryFlag { Emoji = "🇿🇲", Unicode = "U+1F1FF U+1F1F2" }}, 519 | {"ZW", new CountryFlag { Emoji = "🇿🇼", Unicode = "U+1F1FF U+1F1FC" }} 520 | }; 521 | 522 | // Static dictionary for country currencies 523 | private static readonly Dictionary CountriesCurrencies = new Dictionary 524 | { 525 | {"AD", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 526 | {"AE", new CountryCurrency { Code = "AED", Symbol = "د.إ" }}, 527 | {"AF", new CountryCurrency { Code = "AFN", Symbol = "؋" }}, 528 | {"AG", new CountryCurrency { Code = "XCD", Symbol = "$" }}, 529 | {"AI", new CountryCurrency { Code = "XCD", Symbol = "$" }}, 530 | {"AL", new CountryCurrency { Code = "ALL", Symbol = "L" }}, 531 | {"AM", new CountryCurrency { Code = "AMD", Symbol = "֏" }}, 532 | {"AO", new CountryCurrency { Code = "AOA", Symbol = "Kz" }}, 533 | {"AQ", new CountryCurrency { Code = "", Symbol = "$" }}, 534 | {"AR", new CountryCurrency { Code = "ARS", Symbol = "$" }}, 535 | {"AS", new CountryCurrency { Code = "USD", Symbol = "$" }}, 536 | {"AT", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 537 | {"AU", new CountryCurrency { Code = "AUD", Symbol = "$" }}, 538 | {"AW", new CountryCurrency { Code = "AWG", Symbol = "ƒ" }}, 539 | {"AX", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 540 | {"AZ", new CountryCurrency { Code = "AZN", Symbol = "₼" }}, 541 | {"BA", new CountryCurrency { Code = "BAM", Symbol = "KM" }}, 542 | {"BB", new CountryCurrency { Code = "BBD", Symbol = "$" }}, 543 | {"BD", new CountryCurrency { Code = "BDT", Symbol = "৳" }}, 544 | {"BE", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 545 | {"BF", new CountryCurrency { Code = "XOF", Symbol = "CFA" }}, 546 | {"BG", new CountryCurrency { Code = "BGN", Symbol = "лв" }}, 547 | {"BH", new CountryCurrency { Code = "BHD", Symbol = ".د.ب" }}, 548 | {"BI", new CountryCurrency { Code = "BIF", Symbol = "FBu" }}, 549 | {"BJ", new CountryCurrency { Code = "XOF", Symbol = "CFA" }}, 550 | {"BL", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 551 | {"BM", new CountryCurrency { Code = "BMD", Symbol = "$" }}, 552 | {"BN", new CountryCurrency { Code = "BND", Symbol = "$" }}, 553 | {"BO", new CountryCurrency { Code = "BOB", Symbol = "$b" }}, 554 | {"BQ", new CountryCurrency { Code = "USD", Symbol = "$" }}, 555 | {"BR", new CountryCurrency { Code = "BRL", Symbol = "R$" }}, 556 | {"BS", new CountryCurrency { Code = "BSD", Symbol = "$" }}, 557 | {"BT", new CountryCurrency { Code = "BTN", Symbol = "Nu." }}, 558 | {"BV", new CountryCurrency { Code = "NOK", Symbol = "kr" }}, 559 | {"BW", new CountryCurrency { Code = "BWP", Symbol = "P" }}, 560 | {"BY", new CountryCurrency { Code = "BYR", Symbol = "Br" }}, 561 | {"BZ", new CountryCurrency { Code = "BZD", Symbol = "BZ$" }}, 562 | {"CA", new CountryCurrency { Code = "CAD", Symbol = "$" }}, 563 | {"CC", new CountryCurrency { Code = "AUD", Symbol = "$" }}, 564 | {"CD", new CountryCurrency { Code = "CDF", Symbol = "FC" }}, 565 | {"CF", new CountryCurrency { Code = "XAF", Symbol = "FCFA" }}, 566 | {"CG", new CountryCurrency { Code = "XAF", Symbol = "FCFA" }}, 567 | {"CH", new CountryCurrency { Code = "CHF", Symbol = "CHF" }}, 568 | {"CI", new CountryCurrency { Code = "XOF", Symbol = "CFA" }}, 569 | {"CK", new CountryCurrency { Code = "NZD", Symbol = "$" }}, 570 | {"CL", new CountryCurrency { Code = "CLP", Symbol = "$" }}, 571 | {"CM", new CountryCurrency { Code = "XAF", Symbol = "FCFA" }}, 572 | {"CN", new CountryCurrency { Code = "CNY", Symbol = "¥" }}, 573 | {"CO", new CountryCurrency { Code = "COP", Symbol = "$" }}, 574 | {"CR", new CountryCurrency { Code = "CRC", Symbol = "₡" }}, 575 | {"CU", new CountryCurrency { Code = "CUP", Symbol = "₱" }}, 576 | {"CV", new CountryCurrency { Code = "CVE", Symbol = "$" }}, 577 | {"CW", new CountryCurrency { Code = "ANG", Symbol = "ƒ" }}, 578 | {"CX", new CountryCurrency { Code = "AUD", Symbol = "$" }}, 579 | {"CY", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 580 | {"CZ", new CountryCurrency { Code = "CZK", Symbol = "Kč" }}, 581 | {"DE", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 582 | {"DJ", new CountryCurrency { Code = "DJF", Symbol = "Fdj" }}, 583 | {"DK", new CountryCurrency { Code = "DKK", Symbol = "kr" }}, 584 | {"DM", new CountryCurrency { Code = "XCD", Symbol = "$" }}, 585 | {"DO", new CountryCurrency { Code = "DOP", Symbol = "RD$" }}, 586 | {"DZ", new CountryCurrency { Code = "DZD", Symbol = "دج" }}, 587 | {"EC", new CountryCurrency { Code = "USD", Symbol = "$" }}, 588 | {"EE", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 589 | {"EG", new CountryCurrency { Code = "EGP", Symbol = "£" }}, 590 | {"EH", new CountryCurrency { Code = "MAD", Symbol = "MAD" }}, 591 | {"ER", new CountryCurrency { Code = "ERN", Symbol = "Nfk" }}, 592 | {"ES", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 593 | {"ET", new CountryCurrency { Code = "ETB", Symbol = "Br" }}, 594 | {"FI", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 595 | {"FJ", new CountryCurrency { Code = "FJD", Symbol = "$" }}, 596 | {"FK", new CountryCurrency { Code = "FKP", Symbol = "£" }}, 597 | {"FM", new CountryCurrency { Code = "USD", Symbol = "$" }}, 598 | {"FO", new CountryCurrency { Code = "DKK", Symbol = "kr" }}, 599 | {"FR", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 600 | {"GA", new CountryCurrency { Code = "XAF", Symbol = "FCFA" }}, 601 | {"GB", new CountryCurrency { Code = "GBP", Symbol = "£" }}, 602 | {"GD", new CountryCurrency { Code = "XCD", Symbol = "$" }}, 603 | {"GE", new CountryCurrency { Code = "GEL", Symbol = "ლ" }}, 604 | {"GF", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 605 | {"GG", new CountryCurrency { Code = "GBP", Symbol = "£" }}, 606 | {"GH", new CountryCurrency { Code = "GHS", Symbol = "GH₵" }}, 607 | {"GI", new CountryCurrency { Code = "GIP", Symbol = "£" }}, 608 | {"GL", new CountryCurrency { Code = "DKK", Symbol = "kr" }}, 609 | {"GM", new CountryCurrency { Code = "GMD", Symbol = "D" }}, 610 | {"GN", new CountryCurrency { Code = "GNF", Symbol = "FG" }}, 611 | {"GP", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 612 | {"GQ", new CountryCurrency { Code = "XAF", Symbol = "FCFA" }}, 613 | {"GR", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 614 | {"GS", new CountryCurrency { Code = "GBP", Symbol = "£" }}, 615 | {"GT", new CountryCurrency { Code = "GTQ", Symbol = "Q" }}, 616 | {"GU", new CountryCurrency { Code = "USD", Symbol = "$" }}, 617 | {"GW", new CountryCurrency { Code = "XOF", Symbol = "CFA" }}, 618 | {"GY", new CountryCurrency { Code = "GYD", Symbol = "$" }}, 619 | {"HK", new CountryCurrency { Code = "HKD", Symbol = "$" }}, 620 | {"HM", new CountryCurrency { Code = "AUD", Symbol = "$" }}, 621 | {"HN", new CountryCurrency { Code = "HNL", Symbol = "L" }}, 622 | {"HR", new CountryCurrency { Code = "HRK", Symbol = "kn" }}, 623 | {"HT", new CountryCurrency { Code = "HTG", Symbol = "G" }}, 624 | {"HU", new CountryCurrency { Code = "HUF", Symbol = "Ft" }}, 625 | {"ID", new CountryCurrency { Code = "IDR", Symbol = "Rp" }}, 626 | {"IE", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 627 | {"IL", new CountryCurrency { Code = "ILS", Symbol = "₪" }}, 628 | {"IM", new CountryCurrency { Code = "GBP", Symbol = "£" }}, 629 | {"IN", new CountryCurrency { Code = "INR", Symbol = "₹" }}, 630 | {"IO", new CountryCurrency { Code = "USD", Symbol = "$" }}, 631 | {"IQ", new CountryCurrency { Code = "IQD", Symbol = "ع.د" }}, 632 | {"IR", new CountryCurrency { Code = "IRR", Symbol = "﷼" }}, 633 | {"IS", new CountryCurrency { Code = "ISK", Symbol = "kr" }}, 634 | {"IT", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 635 | {"JE", new CountryCurrency { Code = "GBP", Symbol = "£" }}, 636 | {"JM", new CountryCurrency { Code = "JMD", Symbol = "J$" }}, 637 | {"JO", new CountryCurrency { Code = "JOD", Symbol = "JD" }}, 638 | {"JP", new CountryCurrency { Code = "JPY", Symbol = "¥" }}, 639 | {"KE", new CountryCurrency { Code = "KES", Symbol = "KSh" }}, 640 | {"KG", new CountryCurrency { Code = "KGS", Symbol = "лв" }}, 641 | {"KH", new CountryCurrency { Code = "KHR", Symbol = "៛" }}, 642 | {"KI", new CountryCurrency { Code = "AUD", Symbol = "$" }}, 643 | {"KM", new CountryCurrency { Code = "KMF", Symbol = "CF" }}, 644 | {"KN", new CountryCurrency { Code = "XCD", Symbol = "$" }}, 645 | {"KP", new CountryCurrency { Code = "KPW", Symbol = "₩" }}, 646 | {"KR", new CountryCurrency { Code = "KRW", Symbol = "₩" }}, 647 | {"KW", new CountryCurrency { Code = "KWD", Symbol = "KD" }}, 648 | {"KY", new CountryCurrency { Code = "KYD", Symbol = "$" }}, 649 | {"KZ", new CountryCurrency { Code = "KZT", Symbol = "₸" }}, 650 | {"LA", new CountryCurrency { Code = "LAK", Symbol = "₭" }}, 651 | {"LB", new CountryCurrency { Code = "LBP", Symbol = "£" }}, 652 | {"LC", new CountryCurrency { Code = "XCD", Symbol = "$" }}, 653 | {"LI", new CountryCurrency { Code = "CHF", Symbol = "CHF" }}, 654 | {"LK", new CountryCurrency { Code = "LKR", Symbol = "₨" }}, 655 | {"LR", new CountryCurrency { Code = "LRD", Symbol = "$" }}, 656 | {"LS", new CountryCurrency { Code = "LSL", Symbol = "M" }}, 657 | {"LT", new CountryCurrency { Code = "LTL", Symbol = "Lt" }}, 658 | {"LU", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 659 | {"LV", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 660 | {"LY", new CountryCurrency { Code = "LYD", Symbol = "LD" }}, 661 | {"MA", new CountryCurrency { Code = "MAD", Symbol = "MAD" }}, 662 | {"MC", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 663 | {"MD", new CountryCurrency { Code = "MDL", Symbol = "lei" }}, 664 | {"ME", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 665 | {"MF", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 666 | {"MG", new CountryCurrency { Code = "MGA", Symbol = "Ar" }}, 667 | {"MH", new CountryCurrency { Code = "USD", Symbol = "$" }}, 668 | {"MK", new CountryCurrency { Code = "MKD", Symbol = "ден" }}, 669 | {"ML", new CountryCurrency { Code = "XOF", Symbol = "CFA" }}, 670 | {"MM", new CountryCurrency { Code = "MMK", Symbol = "K" }}, 671 | {"MN", new CountryCurrency { Code = "MNT", Symbol = "₮" }}, 672 | {"MO", new CountryCurrency { Code = "MOP", Symbol = "MOP$" }}, 673 | {"MP", new CountryCurrency { Code = "USD", Symbol = "$" }}, 674 | {"MQ", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 675 | {"MR", new CountryCurrency { Code = "MRO", Symbol = "UM" }}, 676 | {"MS", new CountryCurrency { Code = "XCD", Symbol = "$" }}, 677 | {"MT", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 678 | {"MU", new CountryCurrency { Code = "MUR", Symbol = "₨" }}, 679 | {"MV", new CountryCurrency { Code = "MVR", Symbol = "Rf" }}, 680 | {"MW", new CountryCurrency { Code = "MWK", Symbol = "MK" }}, 681 | {"MX", new CountryCurrency { Code = "MXN", Symbol = "$" }}, 682 | {"MY", new CountryCurrency { Code = "MYR", Symbol = "RM" }}, 683 | {"MZ", new CountryCurrency { Code = "MZN", Symbol = "MT" }}, 684 | {"NA", new CountryCurrency { Code = "NAD", Symbol = "$" }}, 685 | {"NC", new CountryCurrency { Code = "XPF", Symbol = "₣" }}, 686 | {"NE", new CountryCurrency { Code = "XOF", Symbol = "CFA" }}, 687 | {"NF", new CountryCurrency { Code = "AUD", Symbol = "$" }}, 688 | {"NG", new CountryCurrency { Code = "NGN", Symbol = "₦" }}, 689 | {"NI", new CountryCurrency { Code = "NIO", Symbol = "C$" }}, 690 | {"NL", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 691 | {"NO", new CountryCurrency { Code = "NOK", Symbol = "kr" }}, 692 | {"NP", new CountryCurrency { Code = "NPR", Symbol = "₨" }}, 693 | {"NR", new CountryCurrency { Code = "AUD", Symbol = "$" }}, 694 | {"NU", new CountryCurrency { Code = "NZD", Symbol = "$" }}, 695 | {"NZ", new CountryCurrency { Code = "NZD", Symbol = "$" }}, 696 | {"OM", new CountryCurrency { Code = "OMR", Symbol = "﷼" }}, 697 | {"PA", new CountryCurrency { Code = "PAB", Symbol = "B/." }}, 698 | {"PE", new CountryCurrency { Code = "PEN", Symbol = "S/." }}, 699 | {"PF", new CountryCurrency { Code = "XPF", Symbol = "₣" }}, 700 | {"PG", new CountryCurrency { Code = "PGK", Symbol = "K" }}, 701 | {"PH", new CountryCurrency { Code = "PHP", Symbol = "₱" }}, 702 | {"PK", new CountryCurrency { Code = "PKR", Symbol = "₨" }}, 703 | {"PL", new CountryCurrency { Code = "PLN", Symbol = "zł" }}, 704 | {"PM", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 705 | {"PN", new CountryCurrency { Code = "NZD", Symbol = "$" }}, 706 | {"PR", new CountryCurrency { Code = "USD", Symbol = "$" }}, 707 | {"PS", new CountryCurrency { Code = "ILS", Symbol = "₪" }}, 708 | {"PT", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 709 | {"PW", new CountryCurrency { Code = "USD", Symbol = "$" }}, 710 | {"PY", new CountryCurrency { Code = "PYG", Symbol = "Gs" }}, 711 | {"QA", new CountryCurrency { Code = "QAR", Symbol = "﷼" }}, 712 | {"RE", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 713 | {"RO", new CountryCurrency { Code = "RON", Symbol = "lei" }}, 714 | {"RS", new CountryCurrency { Code = "RSD", Symbol = "Дин." }}, 715 | {"RU", new CountryCurrency { Code = "RUB", Symbol = "₽" }}, 716 | {"RW", new CountryCurrency { Code = "RWF", Symbol = "R₣" }}, 717 | {"SA", new CountryCurrency { Code = "SAR", Symbol = "﷼" }}, 718 | {"SB", new CountryCurrency { Code = "SBD", Symbol = "$" }}, 719 | {"SC", new CountryCurrency { Code = "SCR", Symbol = "₨" }}, 720 | {"SD", new CountryCurrency { Code = "SDG", Symbol = "ج.س." }}, 721 | {"SE", new CountryCurrency { Code = "SEK", Symbol = "kr" }}, 722 | {"SG", new CountryCurrency { Code = "SGD", Symbol = "S$" }}, 723 | {"SH", new CountryCurrency { Code = "SHP", Symbol = "£" }}, 724 | {"SI", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 725 | {"SJ", new CountryCurrency { Code = "NOK", Symbol = "kr" }}, 726 | {"SK", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 727 | {"SL", new CountryCurrency { Code = "SLL", Symbol = "Le" }}, 728 | {"SM", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 729 | {"SN", new CountryCurrency { Code = "XOF", Symbol = "CFA" }}, 730 | {"SO", new CountryCurrency { Code = "SOS", Symbol = "S" }}, 731 | {"SR", new CountryCurrency { Code = "SRD", Symbol = "$" }}, 732 | {"SS", new CountryCurrency { Code = "SSP", Symbol = "£" }}, 733 | {"ST", new CountryCurrency { Code = "STD", Symbol = "Db" }}, 734 | {"SV", new CountryCurrency { Code = "USD", Symbol = "$" }}, 735 | {"SX", new CountryCurrency { Code = "ANG", Symbol = "ƒ" }}, 736 | {"SY", new CountryCurrency { Code = "SYP", Symbol = "£" }}, 737 | {"SZ", new CountryCurrency { Code = "SZL", Symbol = "E" }}, 738 | {"TC", new CountryCurrency { Code = "USD", Symbol = "$" }}, 739 | {"TD", new CountryCurrency { Code = "XAF", Symbol = "FCFA" }}, 740 | {"TF", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 741 | {"TG", new CountryCurrency { Code = "XOF", Symbol = "CFA" }}, 742 | {"TH", new CountryCurrency { Code = "THB", Symbol = "฿" }}, 743 | {"TJ", new CountryCurrency { Code = "TJS", Symbol = "SM" }}, 744 | {"TK", new CountryCurrency { Code = "NZD", Symbol = "$" }}, 745 | {"TL", new CountryCurrency { Code = "USD", Symbol = "$" }}, 746 | {"TM", new CountryCurrency { Code = "TMT", Symbol = "T" }}, 747 | {"TN", new CountryCurrency { Code = "TND", Symbol = "د.ت" }}, 748 | {"TO", new CountryCurrency { Code = "TOP", Symbol = "T$" }}, 749 | {"TR", new CountryCurrency { Code = "TRY", Symbol = "₺" }}, 750 | {"TT", new CountryCurrency { Code = "TTD", Symbol = "TT$" }}, 751 | {"TV", new CountryCurrency { Code = "AUD", Symbol = "$" }}, 752 | {"TW", new CountryCurrency { Code = "TWD", Symbol = "NT$" }}, 753 | {"TZ", new CountryCurrency { Code = "TZS", Symbol = "TSh" }}, 754 | {"UA", new CountryCurrency { Code = "UAH", Symbol = "₴" }}, 755 | {"UG", new CountryCurrency { Code = "UGX", Symbol = "USh" }}, 756 | {"UM", new CountryCurrency { Code = "USD", Symbol = "$" }}, 757 | {"US", new CountryCurrency { Code = "USD", Symbol = "$" }}, 758 | {"UY", new CountryCurrency { Code = "UYU", Symbol = "$U" }}, 759 | {"UZ", new CountryCurrency { Code = "UZS", Symbol = "лв" }}, 760 | {"VA", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 761 | {"VC", new CountryCurrency { Code = "XCD", Symbol = "$" }}, 762 | {"VE", new CountryCurrency { Code = "VEF", Symbol = "Bs" }}, 763 | {"VG", new CountryCurrency { Code = "USD", Symbol = "$" }}, 764 | {"VI", new CountryCurrency { Code = "USD", Symbol = "$" }}, 765 | {"VN", new CountryCurrency { Code = "VND", Symbol = "₫" }}, 766 | {"VU", new CountryCurrency { Code = "VUV", Symbol = "VT" }}, 767 | {"WF", new CountryCurrency { Code = "XPF", Symbol = "₣" }}, 768 | {"WS", new CountryCurrency { Code = "WST", Symbol = "WS$" }}, 769 | {"XK", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 770 | {"YE", new CountryCurrency { Code = "YER", Symbol = "﷼" }}, 771 | {"YT", new CountryCurrency { Code = "EUR", Symbol = "€" }}, 772 | {"ZA", new CountryCurrency { Code = "ZAR", Symbol = "R" }}, 773 | {"ZM", new CountryCurrency { Code = "ZMK", Symbol = "ZK" }}, 774 | {"ZW", new CountryCurrency { Code = "ZWL", Symbol = "$" }} 775 | }; 776 | 777 | // Static dictionary for continents 778 | private static readonly Dictionary Continents = new Dictionary 779 | { 780 | {"BD", new Continent { Code = "AS", Name = "Asia" }}, 781 | {"BE", new Continent { Code = "EU", Name = "Europe" }}, 782 | {"BF", new Continent { Code = "AF", Name = "Africa" }}, 783 | {"BG", new Continent { Code = "EU", Name = "Europe" }}, 784 | {"BA", new Continent { Code = "EU", Name = "Europe" }}, 785 | {"BB", new Continent { Code = "NA", Name = "North America" }}, 786 | {"WF", new Continent { Code = "OC", Name = "Oceania" }}, 787 | {"BL", new Continent { Code = "NA", Name = "North America" }}, 788 | {"BM", new Continent { Code = "NA", Name = "North America" }}, 789 | {"BN", new Continent { Code = "AS", Name = "Asia" }}, 790 | {"BO", new Continent { Code = "SA", Name = "South America" }}, 791 | {"BH", new Continent { Code = "AS", Name = "Asia" }}, 792 | {"BI", new Continent { Code = "AF", Name = "Africa" }}, 793 | {"BJ", new Continent { Code = "AF", Name = "Africa" }}, 794 | {"BT", new Continent { Code = "AS", Name = "Asia" }}, 795 | {"JM", new Continent { Code = "NA", Name = "North America" }}, 796 | {"BV", new Continent { Code = "AN", Name = "Antarctica" }}, 797 | {"BW", new Continent { Code = "AF", Name = "Africa" }}, 798 | {"WS", new Continent { Code = "OC", Name = "Oceania" }}, 799 | {"BQ", new Continent { Code = "NA", Name = "North America" }}, 800 | {"BR", new Continent { Code = "SA", Name = "South America" }}, 801 | {"BS", new Continent { Code = "NA", Name = "North America" }}, 802 | {"JE", new Continent { Code = "EU", Name = "Europe" }}, 803 | {"BY", new Continent { Code = "EU", Name = "Europe" }}, 804 | {"BZ", new Continent { Code = "NA", Name = "North America" }}, 805 | {"RU", new Continent { Code = "EU", Name = "Europe" }}, 806 | {"RW", new Continent { Code = "AF", Name = "Africa" }}, 807 | {"RS", new Continent { Code = "EU", Name = "Europe" }}, 808 | {"TL", new Continent { Code = "OC", Name = "Oceania" }}, 809 | {"RE", new Continent { Code = "AF", Name = "Africa" }}, 810 | {"TM", new Continent { Code = "AS", Name = "Asia" }}, 811 | {"TJ", new Continent { Code = "AS", Name = "Asia" }}, 812 | {"RO", new Continent { Code = "EU", Name = "Europe" }}, 813 | {"TK", new Continent { Code = "OC", Name = "Oceania" }}, 814 | {"GW", new Continent { Code = "AF", Name = "Africa" }}, 815 | {"GU", new Continent { Code = "OC", Name = "Oceania" }}, 816 | {"GT", new Continent { Code = "NA", Name = "North America" }}, 817 | {"GS", new Continent { Code = "AN", Name = "Antarctica" }}, 818 | {"GR", new Continent { Code = "EU", Name = "Europe" }}, 819 | {"GQ", new Continent { Code = "AF", Name = "Africa" }}, 820 | {"GP", new Continent { Code = "NA", Name = "North America" }}, 821 | {"JP", new Continent { Code = "AS", Name = "Asia" }}, 822 | {"GY", new Continent { Code = "SA", Name = "South America" }}, 823 | {"GG", new Continent { Code = "EU", Name = "Europe" }}, 824 | {"GF", new Continent { Code = "SA", Name = "South America" }}, 825 | {"GE", new Continent { Code = "AS", Name = "Asia" }}, 826 | {"GD", new Continent { Code = "NA", Name = "North America" }}, 827 | {"GB", new Continent { Code = "EU", Name = "Europe" }}, 828 | {"GA", new Continent { Code = "AF", Name = "Africa" }}, 829 | {"SV", new Continent { Code = "NA", Name = "North America" }}, 830 | {"GN", new Continent { Code = "AF", Name = "Africa" }}, 831 | {"GM", new Continent { Code = "AF", Name = "Africa" }}, 832 | {"GL", new Continent { Code = "NA", Name = "North America" }}, 833 | {"GI", new Continent { Code = "EU", Name = "Europe" }}, 834 | {"GH", new Continent { Code = "AF", Name = "Africa" }}, 835 | {"OM", new Continent { Code = "AS", Name = "Asia" }}, 836 | {"TN", new Continent { Code = "AF", Name = "Africa" }}, 837 | {"JO", new Continent { Code = "AS", Name = "Asia" }}, 838 | {"HR", new Continent { Code = "EU", Name = "Europe" }}, 839 | {"HT", new Continent { Code = "NA", Name = "North America" }}, 840 | {"HU", new Continent { Code = "EU", Name = "Europe" }}, 841 | {"HK", new Continent { Code = "AS", Name = "Asia" }}, 842 | {"HN", new Continent { Code = "NA", Name = "North America" }}, 843 | {"HM", new Continent { Code = "AN", Name = "Antarctica" }}, 844 | {"VE", new Continent { Code = "SA", Name = "South America" }}, 845 | {"PR", new Continent { Code = "NA", Name = "North America" }}, 846 | {"PS", new Continent { Code = "AS", Name = "Asia" }}, 847 | {"PW", new Continent { Code = "OC", Name = "Oceania" }}, 848 | {"PT", new Continent { Code = "EU", Name = "Europe" }}, 849 | {"SJ", new Continent { Code = "EU", Name = "Europe" }}, 850 | {"PY", new Continent { Code = "SA", Name = "South America" }}, 851 | {"IQ", new Continent { Code = "AS", Name = "Asia" }}, 852 | {"PA", new Continent { Code = "NA", Name = "North America" }}, 853 | {"PF", new Continent { Code = "OC", Name = "Oceania" }}, 854 | {"PG", new Continent { Code = "OC", Name = "Oceania" }}, 855 | {"PE", new Continent { Code = "SA", Name = "South America" }}, 856 | {"PK", new Continent { Code = "AS", Name = "Asia" }}, 857 | {"PH", new Continent { Code = "AS", Name = "Asia" }}, 858 | {"PN", new Continent { Code = "OC", Name = "Oceania" }}, 859 | {"PL", new Continent { Code = "EU", Name = "Europe" }}, 860 | {"PM", new Continent { Code = "NA", Name = "North America" }}, 861 | {"ZM", new Continent { Code = "AF", Name = "Africa" }}, 862 | {"EH", new Continent { Code = "AF", Name = "Africa" }}, 863 | {"EE", new Continent { Code = "EU", Name = "Europe" }}, 864 | {"EG", new Continent { Code = "AF", Name = "Africa" }}, 865 | {"ZA", new Continent { Code = "AF", Name = "Africa" }}, 866 | {"EC", new Continent { Code = "SA", Name = "South America" }}, 867 | {"IT", new Continent { Code = "EU", Name = "Europe" }}, 868 | {"VN", new Continent { Code = "AS", Name = "Asia" }}, 869 | {"SB", new Continent { Code = "OC", Name = "Oceania" }}, 870 | {"ET", new Continent { Code = "AF", Name = "Africa" }}, 871 | {"SO", new Continent { Code = "AF", Name = "Africa" }}, 872 | {"ZW", new Continent { Code = "AF", Name = "Africa" }}, 873 | {"SA", new Continent { Code = "AS", Name = "Asia" }}, 874 | {"ES", new Continent { Code = "EU", Name = "Europe" }}, 875 | {"ER", new Continent { Code = "AF", Name = "Africa" }}, 876 | {"ME", new Continent { Code = "EU", Name = "Europe" }}, 877 | {"MD", new Continent { Code = "EU", Name = "Europe" }}, 878 | {"MG", new Continent { Code = "AF", Name = "Africa" }}, 879 | {"MF", new Continent { Code = "NA", Name = "North America" }}, 880 | {"MA", new Continent { Code = "AF", Name = "Africa" }}, 881 | {"MC", new Continent { Code = "EU", Name = "Europe" }}, 882 | {"UZ", new Continent { Code = "AS", Name = "Asia" }}, 883 | {"MM", new Continent { Code = "AS", Name = "Asia" }}, 884 | {"ML", new Continent { Code = "AF", Name = "Africa" }}, 885 | {"MO", new Continent { Code = "AS", Name = "Asia" }}, 886 | {"MN", new Continent { Code = "AS", Name = "Asia" }}, 887 | {"MH", new Continent { Code = "OC", Name = "Oceania" }}, 888 | {"MK", new Continent { Code = "EU", Name = "Europe" }}, 889 | {"MU", new Continent { Code = "AF", Name = "Africa" }}, 890 | {"MT", new Continent { Code = "EU", Name = "Europe" }}, 891 | {"MW", new Continent { Code = "AF", Name = "Africa" }}, 892 | {"MV", new Continent { Code = "AS", Name = "Asia" }}, 893 | {"MQ", new Continent { Code = "NA", Name = "North America" }}, 894 | {"MP", new Continent { Code = "OC", Name = "Oceania" }}, 895 | {"MS", new Continent { Code = "NA", Name = "North America" }}, 896 | {"MR", new Continent { Code = "AF", Name = "Africa" }}, 897 | {"IM", new Continent { Code = "EU", Name = "Europe" }}, 898 | {"UG", new Continent { Code = "AF", Name = "Africa" }}, 899 | {"TZ", new Continent { Code = "AF", Name = "Africa" }}, 900 | {"MY", new Continent { Code = "AS", Name = "Asia" }}, 901 | {"MX", new Continent { Code = "NA", Name = "North America" }}, 902 | {"IL", new Continent { Code = "AS", Name = "Asia" }}, 903 | {"FR", new Continent { Code = "EU", Name = "Europe" }}, 904 | {"IO", new Continent { Code = "AS", Name = "Asia" }}, 905 | {"SH", new Continent { Code = "AF", Name = "Africa" }}, 906 | {"FI", new Continent { Code = "EU", Name = "Europe" }}, 907 | {"FJ", new Continent { Code = "OC", Name = "Oceania" }}, 908 | {"FK", new Continent { Code = "SA", Name = "South America" }}, 909 | {"FM", new Continent { Code = "OC", Name = "Oceania" }}, 910 | {"FO", new Continent { Code = "EU", Name = "Europe" }}, 911 | {"NI", new Continent { Code = "NA", Name = "North America" }}, 912 | {"NL", new Continent { Code = "EU", Name = "Europe" }}, 913 | {"NO", new Continent { Code = "EU", Name = "Europe" }}, 914 | {"NA", new Continent { Code = "AF", Name = "Africa" }}, 915 | {"VU", new Continent { Code = "OC", Name = "Oceania" }}, 916 | {"NC", new Continent { Code = "OC", Name = "Oceania" }}, 917 | {"NE", new Continent { Code = "AF", Name = "Africa" }}, 918 | {"NF", new Continent { Code = "OC", Name = "Oceania" }}, 919 | {"NG", new Continent { Code = "AF", Name = "Africa" }}, 920 | {"NZ", new Continent { Code = "OC", Name = "Oceania" }}, 921 | {"NP", new Continent { Code = "AS", Name = "Asia" }}, 922 | {"NR", new Continent { Code = "OC", Name = "Oceania" }}, 923 | {"NU", new Continent { Code = "OC", Name = "Oceania" }}, 924 | {"CK", new Continent { Code = "OC", Name = "Oceania" }}, 925 | {"XK", new Continent { Code = "EU", Name = "Europe" }}, 926 | {"CI", new Continent { Code = "AF", Name = "Africa" }}, 927 | {"CH", new Continent { Code = "EU", Name = "Europe" }}, 928 | {"CO", new Continent { Code = "SA", Name = "South America" }}, 929 | {"CN", new Continent { Code = "AS", Name = "Asia" }}, 930 | {"CM", new Continent { Code = "AF", Name = "Africa" }}, 931 | {"CL", new Continent { Code = "SA", Name = "South America" }}, 932 | {"CC", new Continent { Code = "AS", Name = "Asia" }}, 933 | {"CA", new Continent { Code = "NA", Name = "North America" }}, 934 | {"CG", new Continent { Code = "AF", Name = "Africa" }}, 935 | {"CF", new Continent { Code = "AF", Name = "Africa" }}, 936 | {"CD", new Continent { Code = "AF", Name = "Africa" }}, 937 | {"CZ", new Continent { Code = "EU", Name = "Europe" }}, 938 | {"CY", new Continent { Code = "EU", Name = "Europe" }}, 939 | {"CX", new Continent { Code = "AS", Name = "Asia" }}, 940 | {"CR", new Continent { Code = "NA", Name = "North America" }}, 941 | {"CW", new Continent { Code = "NA", Name = "North America" }}, 942 | {"CV", new Continent { Code = "AF", Name = "Africa" }}, 943 | {"CU", new Continent { Code = "NA", Name = "North America" }}, 944 | {"SZ", new Continent { Code = "AF", Name = "Africa" }}, 945 | {"SY", new Continent { Code = "AS", Name = "Asia" }}, 946 | {"SX", new Continent { Code = "NA", Name = "North America" }}, 947 | {"KG", new Continent { Code = "AS", Name = "Asia" }}, 948 | {"KE", new Continent { Code = "AF", Name = "Africa" }}, 949 | {"SS", new Continent { Code = "AF", Name = "Africa" }}, 950 | {"SR", new Continent { Code = "SA", Name = "South America" }}, 951 | {"KI", new Continent { Code = "OC", Name = "Oceania" }}, 952 | {"KH", new Continent { Code = "AS", Name = "Asia" }}, 953 | {"KN", new Continent { Code = "NA", Name = "North America" }}, 954 | {"KM", new Continent { Code = "AF", Name = "Africa" }}, 955 | {"ST", new Continent { Code = "AF", Name = "Africa" }}, 956 | {"SK", new Continent { Code = "EU", Name = "Europe" }}, 957 | {"KR", new Continent { Code = "AS", Name = "Asia" }}, 958 | {"SI", new Continent { Code = "EU", Name = "Europe" }}, 959 | {"KP", new Continent { Code = "AS", Name = "Asia" }}, 960 | {"KW", new Continent { Code = "AS", Name = "Asia" }}, 961 | {"SN", new Continent { Code = "AF", Name = "Africa" }}, 962 | {"SM", new Continent { Code = "EU", Name = "Europe" }}, 963 | {"SL", new Continent { Code = "AF", Name = "Africa" }}, 964 | {"SC", new Continent { Code = "AF", Name = "Africa" }}, 965 | {"KZ", new Continent { Code = "AS", Name = "Asia" }}, 966 | {"KY", new Continent { Code = "NA", Name = "North America" }}, 967 | {"SG", new Continent { Code = "AS", Name = "Asia" }}, 968 | {"SE", new Continent { Code = "EU", Name = "Europe" }}, 969 | {"SD", new Continent { Code = "AF", Name = "Africa" }}, 970 | {"DO", new Continent { Code = "NA", Name = "North America" }}, 971 | {"DM", new Continent { Code = "NA", Name = "North America" }}, 972 | {"DJ", new Continent { Code = "AF", Name = "Africa" }}, 973 | {"DK", new Continent { Code = "EU", Name = "Europe" }}, 974 | {"VG", new Continent { Code = "NA", Name = "North America" }}, 975 | {"DE", new Continent { Code = "EU", Name = "Europe" }}, 976 | {"YE", new Continent { Code = "AS", Name = "Asia" }}, 977 | {"DZ", new Continent { Code = "AF", Name = "Africa" }}, 978 | {"US", new Continent { Code = "NA", Name = "North America" }}, 979 | {"UY", new Continent { Code = "SA", Name = "South America" }}, 980 | {"YT", new Continent { Code = "AF", Name = "Africa" }}, 981 | {"UM", new Continent { Code = "OC", Name = "Oceania" }}, 982 | {"LB", new Continent { Code = "AS", Name = "Asia" }}, 983 | {"LC", new Continent { Code = "NA", Name = "North America" }}, 984 | {"LA", new Continent { Code = "AS", Name = "Asia" }}, 985 | {"TV", new Continent { Code = "OC", Name = "Oceania" }}, 986 | {"TW", new Continent { Code = "AS", Name = "Asia" }}, 987 | {"TT", new Continent { Code = "NA", Name = "North America" }}, 988 | {"TR", new Continent { Code = "AS", Name = "Asia" }}, 989 | {"LK", new Continent { Code = "AS", Name = "Asia" }}, 990 | {"LI", new Continent { Code = "EU", Name = "Europe" }}, 991 | {"LV", new Continent { Code = "EU", Name = "Europe" }}, 992 | {"TO", new Continent { Code = "OC", Name = "Oceania" }}, 993 | {"LT", new Continent { Code = "EU", Name = "Europe" }}, 994 | {"LU", new Continent { Code = "EU", Name = "Europe" }}, 995 | {"LR", new Continent { Code = "AF", Name = "Africa" }}, 996 | {"LS", new Continent { Code = "AF", Name = "Africa" }}, 997 | {"TH", new Continent { Code = "AS", Name = "Asia" }}, 998 | {"TF", new Continent { Code = "AN", Name = "Antarctica" }}, 999 | {"TG", new Continent { Code = "AF", Name = "Africa" }}, 1000 | {"TD", new Continent { Code = "AF", Name = "Africa" }}, 1001 | {"TC", new Continent { Code = "NA", Name = "North America" }}, 1002 | {"LY", new Continent { Code = "AF", Name = "Africa" }}, 1003 | {"VA", new Continent { Code = "EU", Name = "Europe" }}, 1004 | {"VC", new Continent { Code = "NA", Name = "North America" }}, 1005 | {"AE", new Continent { Code = "AS", Name = "Asia" }}, 1006 | {"AD", new Continent { Code = "EU", Name = "Europe" }}, 1007 | {"AG", new Continent { Code = "NA", Name = "North America" }}, 1008 | {"AF", new Continent { Code = "AS", Name = "Asia" }}, 1009 | {"AI", new Continent { Code = "NA", Name = "North America" }}, 1010 | {"VI", new Continent { Code = "NA", Name = "North America" }}, 1011 | {"IS", new Continent { Code = "EU", Name = "Europe" }}, 1012 | {"IR", new Continent { Code = "AS", Name = "Asia" }}, 1013 | {"AM", new Continent { Code = "AS", Name = "Asia" }}, 1014 | {"AL", new Continent { Code = "EU", Name = "Europe" }}, 1015 | {"AO", new Continent { Code = "AF", Name = "Africa" }}, 1016 | {"AQ", new Continent { Code = "AN", Name = "Antarctica" }}, 1017 | {"AS", new Continent { Code = "OC", Name = "Oceania" }}, 1018 | {"AR", new Continent { Code = "SA", Name = "South America" }}, 1019 | {"AU", new Continent { Code = "OC", Name = "Oceania" }}, 1020 | {"AT", new Continent { Code = "EU", Name = "Europe" }}, 1021 | {"AW", new Continent { Code = "NA", Name = "North America" }}, 1022 | {"IN", new Continent { Code = "AS", Name = "Asia" }}, 1023 | {"AX", new Continent { Code = "EU", Name = "Europe" }}, 1024 | {"AZ", new Continent { Code = "AS", Name = "Asia" }}, 1025 | {"IE", new Continent { Code = "EU", Name = "Europe" }}, 1026 | {"ID", new Continent { Code = "AS", Name = "Asia" }}, 1027 | {"UA", new Continent { Code = "EU", Name = "Europe" }}, 1028 | {"QA", new Continent { Code = "AS", Name = "Asia" }}, 1029 | {"MZ", new Continent { Code = "AF", Name = "Africa" }} 1030 | }; 1031 | 1032 | // Static list for EU countries 1033 | private static readonly List EUCountries = new List 1034 | { 1035 | "IE", 1036 | "AT", 1037 | "LT", 1038 | "LU", 1039 | "LV", 1040 | "DE", 1041 | "DK", 1042 | "SE", 1043 | "SI", 1044 | "SK", 1045 | "CZ", 1046 | "CY", 1047 | "NL", 1048 | "FI", 1049 | "FR", 1050 | "MT", 1051 | "ES", 1052 | "IT", 1053 | "EE", 1054 | "PL", 1055 | "PT", 1056 | "HU", 1057 | "HR", 1058 | "GR", 1059 | "RO", 1060 | "BG", 1061 | "BE" 1062 | }; 1063 | 1064 | /// 1065 | /// Gets full country name against country code. 1066 | /// 1067 | /// Country code consisting of two characters. 1068 | /// The full country name. If country code is not found in the dictionary, then same country code is returned. 1069 | internal static string GetCountry(string countryCode) 1070 | { 1071 | if (countryCode == null) 1072 | { 1073 | return null; 1074 | } 1075 | 1076 | if (Countries.ContainsKey(countryCode)) 1077 | { 1078 | return Countries[countryCode]; 1079 | } 1080 | else 1081 | { 1082 | return countryCode; 1083 | } 1084 | } 1085 | 1086 | /// 1087 | /// whether a country is a member of the EU or not. 1088 | /// 1089 | /// Country code consisting of two characters. 1090 | /// True if the country is a member of the European Union (EU) else false. 1091 | internal static bool IsEU(string countryCode) 1092 | { 1093 | if (countryCode == null) 1094 | { 1095 | return false; 1096 | } 1097 | return EUCountries.Contains(countryCode); 1098 | } 1099 | 1100 | /// 1101 | /// Gets country flag against country code. 1102 | /// "PK" -> CountryFlag:{Emoji:"🇵🇰", Unicode:"U+1F1F5 U+1F1F0"} 1103 | /// 1104 | /// Country code consisting of two characters. 1105 | /// CountryFlag of the country. 1106 | internal static CountryFlag GetCountryFlag(string countryCode) 1107 | { 1108 | if (countryCode == null) 1109 | { 1110 | return null; 1111 | } 1112 | 1113 | if (CountriesFlags.ContainsKey(countryCode)) 1114 | { 1115 | return CountriesFlags[countryCode]; 1116 | } 1117 | else 1118 | { 1119 | return null; 1120 | } 1121 | } 1122 | 1123 | /// 1124 | /// Gets country currency against country code. 1125 | /// {"PK" -> CountryCurrency:{ "code", "PKR"} ,{"symbol", "₨"}} 1126 | /// 1127 | /// Country code consisting of two characters. 1128 | /// CountryCurrency of the country. 1129 | internal static CountryCurrency GetCountryCurrency(string countryCode) 1130 | { 1131 | if (countryCode == null) 1132 | { 1133 | return null; 1134 | } 1135 | 1136 | if (CountriesCurrencies.ContainsKey(countryCode)) 1137 | { 1138 | return CountriesCurrencies[countryCode]; 1139 | } 1140 | else 1141 | { 1142 | return null; 1143 | } 1144 | } 1145 | 1146 | /// 1147 | /// Gets continent against country code. 1148 | /// {"PK" -> Continent:{ "code", "AS"} ,{"name", "Asia"}} 1149 | /// 1150 | /// Country code consisting of two characters. 1151 | /// Continent. 1152 | internal static Continent GetContinent(string countryCode) 1153 | { 1154 | if (countryCode == null) 1155 | { 1156 | return null; 1157 | } 1158 | 1159 | if (Continents.ContainsKey(countryCode)) 1160 | { 1161 | return Continents[countryCode]; 1162 | } 1163 | else 1164 | { 1165 | return null; 1166 | } 1167 | } 1168 | } 1169 | } 1170 | -------------------------------------------------------------------------------- /src/IPinfo/Utilities/JsonHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json; 3 | 4 | using IPinfo.Models; 5 | 6 | namespace IPinfo.Utilities 7 | { 8 | /// 9 | /// JsonHelper class contains json response parsing helper methods. 10 | /// 11 | internal static class JsonHelper 12 | { 13 | private const bool DefaultCaseInsensitive = true; 14 | private const string CountryFlagURL = "https://cdn.ipinfo.io/static/images/countries-flags/"; 15 | 16 | /// 17 | /// JSON Deserialization of a given json string. Case Insensitivity is set to true if options is null. 18 | /// 19 | /// The json string to be deserialize into object. 20 | /// The options to be used for deserialization. 21 | /// The deserialized object. 22 | internal static T Deserialize(string json, JsonSerializerOptions options = null) 23 | { 24 | if (string.IsNullOrWhiteSpace(json)) 25 | { 26 | return default; 27 | } 28 | 29 | if(options is null) 30 | { 31 | options = new JsonSerializerOptions 32 | { 33 | PropertyNameCaseInsensitive = DefaultCaseInsensitive 34 | }; 35 | } 36 | 37 | return JsonSerializer.Deserialize(json, options); 38 | } 39 | 40 | /// 41 | /// JSON Deserialization of a given json string. 42 | /// 43 | /// The json string to be deserialize into object. 44 | /// The boolean options if Property Names should be case insensitive. 45 | /// The deserialized object. 46 | internal static T Deserialize(string json, bool caseInsensitive) 47 | { 48 | JsonSerializerOptions options = new JsonSerializerOptions 49 | { 50 | PropertyNameCaseInsensitive = caseInsensitive 51 | }; 52 | 53 | return Deserialize(json, options); 54 | } 55 | 56 | /// 57 | /// JSON Serialization of a given object. 58 | /// 59 | /// The object to serialize into JSON. 60 | /// The serialized Json string representation of the given object. 61 | internal static string Serialize(object obj) 62 | { 63 | if (obj == null) 64 | { 65 | return null; 66 | } 67 | 68 | return JsonSerializer.Serialize(obj); 69 | } 70 | 71 | /// 72 | /// IPResponse object with extra manual parsing. 73 | /// 74 | /// The json string to be parsed. 75 | /// The deserialized IPResponse object with extra parsing for latitude, longitude, and country being done. 76 | internal static IPResponse ParseIPResponse(string response){ 77 | IPResponse responseModel = JsonHelper.Deserialize(response); 78 | 79 | if(!String.IsNullOrEmpty(responseModel.Loc)) 80 | { 81 | // splitting loc string in "latitude,longitude" fromat 82 | string[] latLongString = responseModel.Loc.Split(','); 83 | if(latLongString.Length == 2) 84 | { 85 | responseModel.Latitude = latLongString[0]; 86 | responseModel.Longitude = latLongString[1]; 87 | } 88 | } 89 | 90 | responseModel.CountryName = CountryHelper.GetCountry(responseModel.Country); 91 | responseModel.IsEU = CountryHelper.IsEU(responseModel.Country); 92 | responseModel.CountryFlag = CountryHelper.GetCountryFlag(responseModel.Country); 93 | responseModel.CountryCurrency = CountryHelper.GetCountryCurrency(responseModel.Country); 94 | responseModel.Continent = CountryHelper.GetContinent(responseModel.Country); 95 | responseModel.CountryFlagURL = CountryFlagURL + responseModel.Country + ".svg"; 96 | 97 | return responseModel; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/IPinfo/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipinfo/csharp/b4e874c71f2b1ae8883fe3e51ee291d67bce629c/src/IPinfo/icon.png --------------------------------------------------------------------------------