Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/update-readme.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Update README detection lists

on:
release:
types: [published]
workflow_dispatch: {}

permissions:
contents: write
pull-requests: write

jobs:
update-readme:
name: Regenerate detection lists
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' || !startsWith(github.event.release.tag_name, 'serilog-v')

steps:
- uses: actions/checkout@v4

- uses: actions/setup-dotnet@v4
with:
dotnet-version: |
10.0.x

- name: Regenerate README.md detection lists
run: dotnet run --project tools/DeviceDetector.NET.ReadmeGen -c Release

- name: Open pull request
uses: peter-evans/create-pull-request@v7
with:
commit-message: "Update README.md detection lists"
title: "Update README.md detection lists"
body: |
Automated regeneration of the "What Device Detector is able to detect" lists in README.md,
triggered by ${{ github.event_name == 'release' && format('release {0}', github.event.release.tag_name) || 'a manual run' }}.
branch: update-readme-lists
delete-branch: true
add-paths: README.md
39 changes: 27 additions & 12 deletions DeviceDetector.NET.sln
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Icons", "Icons", "{A1B2C3D4
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Icons", "Icons", "{A1B2C3D4-7777-4A2B-9C3D-000000000007}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceDetector.NET.ReadmeGen", "tools\DeviceDetector.NET.ReadmeGen\DeviceDetector.NET.ReadmeGen.csproj", "{61E6CCD6-D23E-4DC6-B735-85AF5C295295}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -252,32 +254,45 @@ Global
{17BF8F06-E6BC-4702-9620-8DE092FA9D7B}.Release|x64.Build.0 = Release|Any CPU
{17BF8F06-E6BC-4702-9620-8DE092FA9D7B}.Release|x86.ActiveCfg = Release|Any CPU
{17BF8F06-E6BC-4702-9620-8DE092FA9D7B}.Release|x86.Build.0 = Release|Any CPU
{61E6CCD6-D23E-4DC6-B735-85AF5C295295}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{61E6CCD6-D23E-4DC6-B735-85AF5C295295}.Debug|Any CPU.Build.0 = Debug|Any CPU
{61E6CCD6-D23E-4DC6-B735-85AF5C295295}.Debug|x64.ActiveCfg = Debug|Any CPU
{61E6CCD6-D23E-4DC6-B735-85AF5C295295}.Debug|x64.Build.0 = Debug|Any CPU
{61E6CCD6-D23E-4DC6-B735-85AF5C295295}.Debug|x86.ActiveCfg = Debug|Any CPU
{61E6CCD6-D23E-4DC6-B735-85AF5C295295}.Debug|x86.Build.0 = Debug|Any CPU
{61E6CCD6-D23E-4DC6-B735-85AF5C295295}.Release|Any CPU.ActiveCfg = Release|Any CPU
{61E6CCD6-D23E-4DC6-B735-85AF5C295295}.Release|Any CPU.Build.0 = Release|Any CPU
{61E6CCD6-D23E-4DC6-B735-85AF5C295295}.Release|x64.ActiveCfg = Release|Any CPU
{61E6CCD6-D23E-4DC6-B735-85AF5C295295}.Release|x64.Build.0 = Release|Any CPU
{61E6CCD6-D23E-4DC6-B735-85AF5C295295}.Release|x86.ActiveCfg = Release|Any CPU
{61E6CCD6-D23E-4DC6-B735-85AF5C295295}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{9D4D2F68-CCD7-49C1-B117-4C642CC6F678} = {999E1778-7E91-4F62-AD53-6BE530E59B7C}
{69FF52A1-6576-413A-9FBB-30F911261511} = {9D4D2F68-CCD7-49C1-B117-4C642CC6F678}
{9D04EA3B-96FC-4B15-AA5E-8F6E6AD31E9C} = {A1B2C3D4-1111-4A2B-9C3D-000000000001}
{24AF45AC-9BF3-46FB-85A3-2FA3C6EDB44A} = {A1B2C3D4-3333-4A2B-9C3D-000000000003}
{94B4F635-61AC-40E4-87CC-DDA4217A0999} = {A1B2C3D4-1111-4A2B-9C3D-000000000001}
{DF4201EA-0991-4A3B-AE89-DB2E6ABB8E9C} = {A1B2C3D4-6666-4A2B-9C3D-000000000006}
{F88615DF-0E16-460A-814E-A288889DE179} = {A1B2C3D4-1111-4A2B-9C3D-000000000001}
{2C6A9F5E-3B3D-4B0A-9E3F-9B4C5D6E7F80} = {A1B2C3D4-4444-4A2B-9C3D-000000000004}
{E4DA874C-7C29-4D34-9C0E-103073AEE043} = {A1B2C3D4-5555-4A2B-9C3D-000000000005}
{2215DEE2-1589-4E3B-A29F-A65A804119EB} = {A1B2C3D4-2222-4A2B-9C3D-000000000002}
{2C6A9F5E-3B3D-4B0A-9E3F-9B4C5D6E7F80} = {A1B2C3D4-4444-4A2B-9C3D-000000000004}
{04D3C595-50EF-4E7B-8DF6-D5F997F4446D} = {A1B2C3D4-2222-4A2B-9C3D-000000000002}
{24AF45AC-9BF3-46FB-85A3-2FA3C6EDB44A} = {A1B2C3D4-3333-4A2B-9C3D-000000000003}
{FF6F8F1A-735D-42C7-A41C-53FCA8C552A3} = {A1B2C3D4-7777-4A2B-9C3D-000000000007}
{9D4D2F68-CCD7-49C1-B117-4C642CC6F678} = {999E1778-7E91-4F62-AD53-6BE530E59B7C}
{69FF52A1-6576-413A-9FBB-30F911261511} = {9D4D2F68-CCD7-49C1-B117-4C642CC6F678}
{F88615DF-0E16-460A-814E-A288889DE179} = {A1B2C3D4-1111-4A2B-9C3D-000000000001}
{6871B4FE-BC7F-4B63-9BE0-31489F685C70} = {A1B2C3D4-3333-4A2B-9C3D-000000000003}
{A1B2C3D4-4444-4A2B-9C3D-000000000004} = {A1B2C3D4-1111-4A2B-9C3D-000000000001}
{A1B2C3D4-5555-4A2B-9C3D-000000000005} = {A1B2C3D4-2222-4A2B-9C3D-000000000002}
{A1B2C3D4-6666-4A2B-9C3D-000000000006} = {A1B2C3D4-1111-4A2B-9C3D-000000000001}
{A1B2C3D4-7777-4A2B-9C3D-000000000007} = {A1B2C3D4-3333-4A2B-9C3D-000000000003}
{2215DEE2-1589-4E3B-A29F-A65A804119EB} = {A1B2C3D4-2222-4A2B-9C3D-000000000002}
{DF4201EA-0991-4A3B-AE89-DB2E6ABB8E9C} = {A1B2C3D4-6666-4A2B-9C3D-000000000006}
{FF6F8F1A-735D-42C7-A41C-53FCA8C552A3} = {A1B2C3D4-7777-4A2B-9C3D-000000000007}
{33D4E06E-6C86-44CC-A898-330CE9D6D962} = {A1B2C3D4-6666-4A2B-9C3D-000000000006}
{15723485-950A-4A1F-97C7-0DECE5AAF2A6} = {A1B2C3D4-7777-4A2B-9C3D-000000000007}
{D4B9BE85-8E2B-4FC6-BB46-D52BD83C9A7F} = {A1B2C3D4-6666-4A2B-9C3D-000000000006}
{17BF8F06-E6BC-4702-9620-8DE092FA9D7B} = {A1B2C3D4-7777-4A2B-9C3D-000000000007}
{A1B2C3D4-4444-4A2B-9C3D-000000000004} = {A1B2C3D4-1111-4A2B-9C3D-000000000001}
{A1B2C3D4-5555-4A2B-9C3D-000000000005} = {A1B2C3D4-2222-4A2B-9C3D-000000000002}
{A1B2C3D4-6666-4A2B-9C3D-000000000006} = {A1B2C3D4-1111-4A2B-9C3D-000000000001}
{A1B2C3D4-7777-4A2B-9C3D-000000000007} = {A1B2C3D4-3333-4A2B-9C3D-000000000003}
{61E6CCD6-D23E-4DC6-B735-85AF5C295295} = {A1B2C3D4-2222-4A2B-9C3D-000000000002}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {88D253CD-9A5E-42A6-BBBA-3362F7D47DF2}
Expand Down
22 changes: 12 additions & 10 deletions README.md

Large diffs are not rendered by default.

35 changes: 1 addition & 34 deletions src/DeviceDetector.NET/Parser/Client/AbstractClientParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,40 +68,7 @@ protected AbstractClientParser(string ua, ClientHints clientHints = null) : base
/// <returns></returns>
public virtual List<string> GetAvailableClients()
{
List<string> names;
names = regexList.Where(r => r.Name != "$1").Select(r => r.Name).ToList();
if (true)
{

names.AddRange(new List<string>
{
// Microsoft Office $1
"Microsoft Office Access",
"Microsoft Office Excel",
"Microsoft Office OneDrive for Business",
"Microsoft Office OneNote",
"Microsoft Office PowerPoint",
"Microsoft Office Project",
"Microsoft Office Publisher",
"Microsoft Office Visio",
"Microsoft Office Word",
// Podkicker$1
"Podkicker",
"Podkicker Pro",
"Podkicker Classic",
// radio.$1
"radio.at",
"radio.de",
"radio.dk",
"radio.es",
"radio.fr",
"radio.it",
"radio.pl",
"radio.pt",
"radio.se",
"radio.net"
});
}
var names = regexList.Where(r => !r.Name.Contains("$1") && !r.Name.Contains("$2")).Select(r => r.Name).ToList();
return names.Distinct().OrderBy(o => o).ToList();
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/DeviceDetector.NET/Parser/Client/Browser/EngineParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public class EngineParser : AbstractClientParser<List<BrowserEngine>>
"EkiohFlow",
"Arachne",
"LibWeb",
"Maple"
"Maple",
"ArkWeb"
};

public EngineParser()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>DeviceDetector.Net.ReadmeGen</RootNamespace>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\DeviceDetector.NET\DeviceDetector.NET.csproj" />
</ItemGroup>

</Project>
108 changes: 108 additions & 0 deletions tools/DeviceDetector.NET.ReadmeGen/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System.Text;
using System.Text.RegularExpressions;
using DeviceDetectorNET.Parser;
using DeviceDetectorNET.Parser.Client;
using DeviceDetectorNET.Parser.Client.Browser;
using DeviceDetectorNET.Parser.Device;

// Regenerates the "## What Device Detector is able to detect" section of README.md
// from the library's own data, mirroring device-detector's misc/updateReadme.php.

string FindRepoRoot()
{
var dir = new DirectoryInfo(Directory.GetCurrentDirectory());
while (dir != null && !File.Exists(Path.Combine(dir.FullName, "DeviceDetector.NET.sln")))
dir = dir.Parent;
return dir?.FullName ?? Directory.GetCurrentDirectory();
}

int NatCompare(string a, string b)
{
var ra = Regex.Matches(a, @"\d+|\D+").Select(m => m.Value).ToArray();
var rb = Regex.Matches(b, @"\d+|\D+").Select(m => m.Value).ToArray();
int len = Math.Min(ra.Length, rb.Length);
for (int i = 0; i < len; i++)
{
string pa = ra[i], pb = rb[i];
bool na = int.TryParse(pa, out int ia);
bool nb = int.TryParse(pb, out int ib);
int cmp = na && nb ? ia.CompareTo(ib) : string.Compare(pa, pb, StringComparison.OrdinalIgnoreCase);
if (cmp != 0) return cmp;
}
return ra.Length.CompareTo(rb.Length);
}

List<string> Sorted(IEnumerable<string> items)
{
var list = items.Distinct().ToList();
list.Sort(NatCompare);
return list;
}

var repoRoot = FindRepoRoot();
var readmePath = Path.Combine(repoRoot, "README.md");
var botsFixturePath = Path.Combine(repoRoot, "tests", "DeviceDetector.NET.Tests", "fixtures", "bots.yml");

var sb = new StringBuilder();
void Line(string s = "") => sb.Append(s).Append('\n');

Line("## What Device Detector is able to detect");
Line();
Line("The lists below are auto generated and updated from time to time. Some of them might not be complete.");
Line();
Line($"*Last update: {DateTime.UtcNow:yyyy/MM/dd}*");
Line();

void Section(string title, IEnumerable<string> items, string? footnote = null)
{
Line($"### {title}:");
Line();
sb.Append(string.Join(", ", Sorted(items)));
if (footnote != null)
sb.Append(footnote);
Line();
Line();
}

Section("List of detected operating systems", OperatingSystemParser.GetAvailableOperatingSystems().Values);
Section("List of detected browsers", BrowserParser.GetAvailableBrowsers().Values);
Section("List of detected browser engines", EngineParser.GetAvailableEngines());
Section("List of detected libraries", new LibraryParser().GetAvailableClients());
Section("List of detected media players", new MediaPlayerParser().GetAvailableClients());
Section(
"List of detected mobile apps",
new MobileAppParser().GetAvailableClients(),
" and *mobile apps using [AFNetworking](https://github.com/AFNetworking/AFNetworking) or [Electron](https://github.com/electron/electron)*"
);
Section("List of detected PIMs (personal information manager)", new PimParser().GetAvailableClients());
Section("List of detected feed readers", new FeedReaderParser().GetAvailableClients());
Section("List of brands with detected devices", Devices.DeviceBrands.Values);

var botsYaml = File.ReadAllText(botsFixturePath);
var botNames = Regex.Matches(botsYaml, @"^ name: (.+)$", RegexOptions.Multiline)
.Select(m => m.Groups[1].Value.Trim())
.Select(n => (n.StartsWith('"') && n.EndsWith('"')) || (n.StartsWith('\'') && n.EndsWith('\''))
? n.Substring(1, n.Length - 2)
: n)
.Where(n => n != "Generic Bot");
Line("### List of detected bots:");
Line();
sb.Append(string.Join(", ", Sorted(botNames)));
Line();

var hasBom = File.ReadAllBytes(readmePath) is [0xEF, 0xBB, 0xBF, ..];
var readme = File.ReadAllText(readmePath);
var markers = new[]
{
"## What Device Detector is able to detect",
"The lists below are auto generated and updated from time to time.",
};
var markerIndex = markers
.Select(m => readme.IndexOf(m, StringComparison.Ordinal))
.Where(i => i >= 0)
.DefaultIfEmpty(-1)
.Min();
var head = markerIndex >= 0 ? readme[..markerIndex] : readme.TrimEnd('\r', '\n') + "\n\n";

File.WriteAllText(readmePath, head + sb, new UTF8Encoding(hasBom));
Console.WriteLine($"README.md detection lists regenerated ({readmePath}).");
Loading