├── .github ├── CODE_OF_CONDUCT.md ├── FUNDING.yml ├── README.md ├── dependabot.yml └── workflows │ ├── directory.yml │ └── web.yml ├── LICENSE ├── img ├── icon_filterlists.png ├── icon_filterlists.psd ├── logo_filterlists.png ├── logo_filterlists.psd ├── logo_filterlists.xcf ├── logo_filterlists_outlined.png ├── logo_filterlists_white.png └── logo_lg_filterlists.png ├── services ├── Directory.Build.props ├── Directory │ ├── FilterLists.Directory.Api │ │ ├── CacheControlFilter.cs │ │ ├── Cors │ │ │ ├── CorsConfiguration.cs │ │ │ └── CorsOptions.cs │ │ ├── Endpoints.cs │ │ ├── FilterLists.Directory.Api.csproj │ │ ├── OpenApi │ │ │ ├── OpenApiGenConfiguration.cs │ │ │ ├── OpenApiTags.cs │ │ │ └── SwaggerUiConfiguration.cs │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ ├── FilterLists.Directory.Application │ │ ├── ConfigurationExtensions.cs │ │ ├── FilterLists.Directory.Application.csproj │ │ └── Queries │ │ │ ├── GetLanguages.cs │ │ │ ├── GetLicenses.cs │ │ │ ├── GetListDetails.cs │ │ │ ├── GetLists.cs │ │ │ ├── GetMaintainers.cs │ │ │ ├── GetSoftware.cs │ │ │ ├── GetSyntaxes.cs │ │ │ └── GetTags.cs │ ├── FilterLists.Directory.Infrastructure.Migrations │ │ ├── FilterLists.Directory.Infrastructure.Migrations.csproj │ │ └── Migrations │ │ │ ├── 20250506231646_InitialCreate.Designer.cs │ │ │ ├── 20250506231646_InitialCreate.cs │ │ │ ├── 20250508015706_4807.Designer.cs │ │ │ ├── 20250508015706_4807.cs │ │ │ ├── 20250510182832_4823.Designer.cs │ │ │ ├── 20250510182832_4823.cs │ │ │ └── QueryDbContextModelSnapshot.cs │ ├── FilterLists.Directory.Infrastructure │ │ ├── ConfigurationExtensions.cs │ │ ├── FilterLists.Directory.Infrastructure.csproj │ │ └── Persistence │ │ │ └── Queries │ │ │ ├── Context │ │ │ └── QueryDbContext.cs │ │ │ ├── Entities │ │ │ ├── Dependent.cs │ │ │ ├── FilterList.cs │ │ │ ├── FilterListLanguage.cs │ │ │ ├── FilterListMaintainer.cs │ │ │ ├── FilterListSyntax.cs │ │ │ ├── FilterListTag.cs │ │ │ ├── FilterListViewUrl.cs │ │ │ ├── Fork.cs │ │ │ ├── Language.cs │ │ │ ├── License.cs │ │ │ ├── Maintainer.cs │ │ │ ├── Merge.cs │ │ │ ├── Software.cs │ │ │ ├── SoftwareSyntax.cs │ │ │ ├── Syntax.cs │ │ │ └── Tag.cs │ │ │ ├── MigrationService.cs │ │ │ └── SeedExtensions.cs │ └── data │ │ ├── Dependent.json │ │ ├── FilterList.json │ │ ├── FilterListLanguage.json │ │ ├── FilterListMaintainer.json │ │ ├── FilterListSyntax.json │ │ ├── FilterListTag.json │ │ ├── FilterListViewUrl.json │ │ ├── Fork.json │ │ ├── Language.json │ │ ├── License.json │ │ ├── Maintainer.json │ │ ├── Merge.json │ │ ├── Software.json │ │ ├── SoftwareSyntax.json │ │ ├── Syntax.json │ │ ├── Tag.json │ │ └── lint.sh ├── FilterLists.AppHost │ ├── AppHost.cs │ ├── FilterLists.AppHost.csproj │ ├── Properties │ │ └── launchSettings.json │ ├── appsettings.Development.json │ └── appsettings.json ├── FilterLists.ServiceDefaults │ ├── Extensions.cs │ └── FilterLists.ServiceDefaults.csproj ├── FilterLists.sln └── FilterLists.sln.DotSettings └── web ├── .prettierrc ├── config-overrides.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── icon_filterlists.png ├── index.html ├── logo_filterlists.png ├── manifest.json ├── robots.txt ├── tpl-redirect.js └── tpl.html ├── src ├── App.test.tsx ├── App.tsx ├── components │ ├── Description.tsx │ ├── LicenseTag.tsx │ ├── LinkButton.tsx │ ├── ListInfoButton.tsx │ ├── SubscribeButtons.tsx │ ├── SyntaxTag.tsx │ ├── ViewButtons.tsx │ ├── index.ts │ ├── languageCloud │ │ ├── LanguageCloud.tsx │ │ └── index.ts │ ├── listInfoDrawer │ │ ├── ListInfoDrawer.tsx │ │ ├── index.ts │ │ └── listInfoDrawer.css │ ├── listsTable │ │ ├── ListDrawer.tsx │ │ ├── ListsTable.tsx │ │ ├── ListsTableHoc.tsx │ │ ├── arraySorter.ts │ │ └── index.ts │ ├── maintainerCloud │ │ ├── MaintainerCloud.tsx │ │ └── index.ts │ ├── maintainers │ │ ├── Maintainers.tsx │ │ ├── index.ts │ │ └── maintainers.css │ ├── softwareCloud │ │ ├── SoftwareCloud.tsx │ │ ├── SoftwareIcon.tsx │ │ ├── imgs │ │ │ ├── 01-uBlock-Origin.svg │ │ │ ├── 02-Adblock-Plus.svg │ │ │ ├── 03-AdGuard.svg │ │ │ ├── 04-DNS66.png │ │ │ ├── 05-Nano-Adblocker.png │ │ │ ├── 06-AdBlock.png │ │ │ ├── 07-AdAway.png │ │ │ ├── 08-Personal-Blocklist.png │ │ │ ├── 10-Redirector.png │ │ │ ├── 11-Hosts-File-Editor.png │ │ │ ├── 12-Gas-Mask.png │ │ │ ├── 13-MinerBlock.svg │ │ │ ├── 14-Pi-hole.svg │ │ │ ├── 15-uBlock.svg │ │ │ ├── 16-Internet-Explorer-TPL.svg │ │ │ ├── 17-Google-Hit-Hider-by-Domain.png │ │ │ ├── 17-Google-Hit-Hider-by-Domain.svg │ │ │ ├── 18-FireHOL.png │ │ │ ├── 19-Samsung-Knox.png │ │ │ ├── 19-Samsung-Knox.svg │ │ │ ├── 20-Little-Snitch.png │ │ │ ├── 21-Privoxy.png │ │ │ ├── 22-Diversion.png │ │ │ ├── 22-Diversion.svg │ │ │ ├── 23-dnsmasq.png │ │ │ ├── 24-Slimjet.png │ │ │ ├── 25-uMatrix.png │ │ │ ├── 26-Blokada.png │ │ │ ├── 27-hostsmgr.png │ │ │ ├── 28-personalDNSfilter.svg │ │ │ ├── 29-Unbound.png │ │ │ ├── 30-BIND.png │ │ │ ├── 31-AdGuard-Home.png │ │ │ ├── 31-AdGuard-Home.svg │ │ │ ├── 32-AdNauseam.png │ │ │ ├── 33-Legacy-Unix-Derivatives.png │ │ │ ├── 34-Windows-command-line.png │ │ │ ├── 35-Shadowsocks.png │ │ │ ├── 36-ShadowsocksR.png │ │ │ ├── 37-Shadowrocket.png │ │ │ ├── 38-DNSRedirector.png │ │ │ ├── 39-pfBlockerNG.png │ │ │ ├── 40-Opera.png │ │ │ ├── 41-Surge.png │ │ │ ├── 42-dnscrypt-proxy.png │ │ │ ├── 43-SmartDNS.png │ │ │ ├── 44-AdGuard-for-WindowsMac.svg │ │ │ ├── 45-AdGuard-for-Android.svg │ │ │ ├── 46-Vivaldi.svg │ │ │ ├── 47-PolishCookieConsent.png │ │ │ ├── imgs.d.ts │ │ │ └── index.ts │ │ └── index.ts │ ├── syntaxCloud │ │ ├── SyntaxCloud.tsx │ │ └── index.ts │ └── tagCloud │ │ ├── TagCloud.tsx │ │ └── index.ts ├── constants.ts ├── hooks │ ├── index.ts │ ├── useApiData.tsx │ ├── useLanguages.tsx │ ├── useLicenses.tsx │ ├── useListDetails.tsx │ ├── useLists.tsx │ ├── useMaintainers.tsx │ ├── useSearchColumnFilter.tsx │ ├── useSoftware.tsx │ ├── useSyntaxes.tsx │ ├── useTablePageSizer.tsx │ └── useTags.tsx ├── index.css ├── index.tsx ├── interfaces │ ├── Language.ts │ ├── License.ts │ ├── List.ts │ ├── ListDetails.ts │ ├── Maintainer.ts │ ├── Software.ts │ ├── Syntax.ts │ └── Tag.ts ├── react-app-env.d.ts ├── reportWebVitals.ts ├── setupProxy.js ├── setupTests.ts └── utils │ ├── index.ts │ └── nameof.ts ├── staticwebapp.config.json └── tsconfig.json /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at collinbarrett@users.noreply.github.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: collinbarrett 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: services 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: npm 8 | directory: /web 9 | schedule: 10 | interval: daily -------------------------------------------------------------------------------- /.github/workflows/directory.yml: -------------------------------------------------------------------------------- 1 | name: Directory API - Build & Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - .github/workflows/directory.yml 9 | - services/Directory.Build.props 10 | - services/Directory/** 11 | - services/FilterLists.ServiceDefaults/** 12 | pull_request: 13 | branches: 14 | - main 15 | paths: 16 | - .github/workflows/directory.yml 17 | - services/Directory.Build.props 18 | - services/Directory/** 19 | - services/FilterLists.ServiceDefaults/** 20 | 21 | env: 22 | CONTAINER_REGISTRY: ghcr.io 23 | CONTAINER_REPOSITORY: collinbarrett/filterlists-directory-api 24 | CONTAINER_IMAGE_TAG_UNIQUE: ${{ github.run_id }} 25 | 26 | jobs: 27 | build: 28 | name: Build 29 | 30 | runs-on: ubuntu-latest 31 | 32 | permissions: 33 | packages: write 34 | 35 | env: 36 | CONTAINER_IMAGE_TAG_STABLE: latest 37 | 38 | steps: 39 | - name: Checkout 40 | uses: actions/checkout@v4 41 | 42 | - name: Setup .NET 43 | uses: actions/setup-dotnet@v4 44 | with: 45 | dotnet-version: "9.0.x" 46 | 47 | - name: Build 48 | run: dotnet build -c Release 49 | working-directory: ./services/Directory/FilterLists.Directory.Api 50 | 51 | - name: Login to Container Registry 52 | if: github.event_name == 'push' 53 | uses: docker/login-action@v3 54 | with: 55 | registry: ${{ env.CONTAINER_REGISTRY }} 56 | username: ${{ github.actor }} 57 | password: ${{ secrets.GITHUB_TOKEN }} 58 | 59 | - name: Publish & Push 60 | if: github.event_name == 'push' 61 | run: > 62 | dotnet publish 63 | -p:PublishProfile=DefaultContainer 64 | -p:ContainerRegistry=${{ env.CONTAINER_REGISTRY }} 65 | -p:ContainerRepository=${{ env.CONTAINER_REPOSITORY }} 66 | -p:ContainerImageTags='"${{ env.CONTAINER_IMAGE_TAG_UNIQUE }};${{ env.CONTAINER_IMAGE_TAG_STABLE }}"' 67 | working-directory: ./services/Directory/FilterLists.Directory.Api 68 | 69 | deploy: 70 | name: Deploy 71 | 72 | runs-on: ubuntu-latest 73 | 74 | needs: build 75 | 76 | permissions: 77 | contents: read 78 | 79 | if: github.event_name == 'push' 80 | 81 | env: 82 | AZURE_WEBAPP_NAME: app-filterlists-directory-prod 83 | 84 | environment: 85 | name: production-directory-api 86 | url: https://api.filterlists.com 87 | 88 | steps: 89 | - name: Deploy to Azure 90 | uses: azure/webapps-deploy@v3 91 | with: 92 | app-name: ${{ env.AZURE_WEBAPP_NAME }} 93 | publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE_APP_FILTERLISTS_DIRECTORY_PROD }} 94 | images: ${{ env.CONTAINER_REGISTRY }}/${{ env.CONTAINER_REPOSITORY }}:${{ env.CONTAINER_IMAGE_TAG_UNIQUE }} 95 | -------------------------------------------------------------------------------- /.github/workflows/web.yml: -------------------------------------------------------------------------------- 1 | name: Web - Build & Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - .github/workflows/web.yml 9 | - web/** 10 | pull_request: 11 | types: [opened, synchronize, reopened, closed] 12 | branches: 13 | - main 14 | paths: 15 | - .github/workflows/web.yml 16 | - web/** 17 | 18 | jobs: 19 | build_and_deploy_staging: 20 | name: Build & Deploy to Staging 21 | 22 | runs-on: ubuntu-latest 23 | 24 | permissions: 25 | contents: read 26 | id-token: write 27 | 28 | if: github.event_name == 'pull_request' && github.event.action != 'closed' 29 | 30 | environment: 31 | name: staging-web 32 | url: ${{ steps.builddeploy.outputs.static_web_app_url }} 33 | 34 | steps: 35 | - name: Checkout 36 | uses: actions/checkout@v4 37 | 38 | - name: Setup Node 39 | uses: actions/setup-node@v4 40 | with: 41 | node-version: lts/* 42 | cache: npm 43 | cache-dependency-path: web/package-lock.json 44 | 45 | - name: Install 46 | run: npm ci 47 | working-directory: ./web 48 | 49 | - name: Run Prettier 50 | run: npm run prettier:check 51 | working-directory: ./web 52 | 53 | - name: Run ESLint 54 | run: npm run eslint:check 55 | working-directory: ./web 56 | 57 | - name: Build 58 | run: npm run build 59 | working-directory: ./web 60 | 61 | - name: Copy Static Web App Config 62 | run: cp staticwebapp.config.json build/ 63 | working-directory: ./web 64 | 65 | - name: Deploy 66 | id: builddeploy 67 | uses: Azure/static-web-apps-deploy@v1 68 | with: 69 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_STAPP_FILTERLISTS_PROD }} 70 | repo_token: ${{ secrets.GITHUB_TOKEN }} 71 | action: upload 72 | app_location: ./web/build 73 | output_location: "" 74 | skip_app_build: true 75 | continue-on-error: true 76 | 77 | build_and_deploy_production: 78 | name: Build & Deploy to Production 79 | 80 | runs-on: ubuntu-latest 81 | 82 | permissions: 83 | contents: read 84 | id-token: write 85 | 86 | if: github.event_name == 'push' 87 | 88 | environment: 89 | name: production-web 90 | url: https://filterlists.com 91 | 92 | steps: 93 | - name: Checkout 94 | uses: actions/checkout@v4 95 | 96 | - name: Setup Node 97 | uses: actions/setup-node@v4 98 | with: 99 | node-version: lts/* 100 | cache: npm 101 | cache-dependency-path: web/package-lock.json 102 | 103 | - name: Install 104 | run: npm ci 105 | working-directory: ./web 106 | 107 | - name: Build 108 | run: npm run build 109 | working-directory: ./web 110 | 111 | - name: Copy Static Web App Config 112 | run: cp staticwebapp.config.json build/ 113 | working-directory: ./web 114 | 115 | - name: Deploy 116 | id: builddeploy 117 | uses: Azure/static-web-apps-deploy@v1 118 | with: 119 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_STAPP_FILTERLISTS_PROD }} 120 | repo_token: ${{ secrets.GITHUB_TOKEN }} 121 | action: upload 122 | app_location: ./web/build 123 | output_location: "" 124 | skip_app_build: true 125 | 126 | close_pull_request: 127 | name: Close Pull Request 128 | 129 | runs-on: ubuntu-latest 130 | 131 | permissions: 132 | contents: read 133 | 134 | if: github.event_name == 'pull_request' && github.event.action == 'closed' 135 | 136 | steps: 137 | - name: Close Pull Request 138 | uses: Azure/static-web-apps-deploy@v1 139 | with: 140 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_STAPP_FILTERLISTS_PROD }} 141 | action: close 142 | app_location: ./web 143 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Collin M. Barrett 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /img/icon_filterlists.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/img/icon_filterlists.png -------------------------------------------------------------------------------- /img/icon_filterlists.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/img/icon_filterlists.psd -------------------------------------------------------------------------------- /img/logo_filterlists.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/img/logo_filterlists.png -------------------------------------------------------------------------------- /img/logo_filterlists.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/img/logo_filterlists.psd -------------------------------------------------------------------------------- /img/logo_filterlists.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/img/logo_filterlists.xcf -------------------------------------------------------------------------------- /img/logo_filterlists_outlined.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/img/logo_filterlists_outlined.png -------------------------------------------------------------------------------- /img/logo_filterlists_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/img/logo_filterlists_white.png -------------------------------------------------------------------------------- /img/logo_lg_filterlists.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/img/logo_lg_filterlists.png -------------------------------------------------------------------------------- /services/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | enable 4 | enable 5 | Recommended 6 | 7 | -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Api/CacheControlFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Net.Http.Headers; 2 | 3 | namespace FilterLists.Directory.Api; 4 | 5 | internal sealed class CacheControlFilter(int clientCacheSeconds, int cdnCacheSeconds) : IEndpointFilter 6 | { 7 | private readonly string _cacheControlHeaderValue = new CacheControlHeaderValue 8 | { 9 | Public = true, 10 | MaxAge = TimeSpan.FromSeconds(clientCacheSeconds), 11 | SharedMaxAge = TimeSpan.FromSeconds(cdnCacheSeconds) 12 | } 13 | .ToString(); 14 | 15 | public async ValueTask InvokeAsync( 16 | EndpointFilterInvocationContext context, 17 | EndpointFilterDelegate next) 18 | { 19 | var result = await next(context); 20 | 21 | if (context.HttpContext.Response.StatusCode is >= 200 and < 300) 22 | context.HttpContext.Response.Headers.CacheControl = _cacheControlHeaderValue; 23 | 24 | return result; 25 | } 26 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Api/Cors/CorsConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace FilterLists.Directory.Api.Cors; 2 | 3 | internal static class CorsConfiguration 4 | { 5 | internal static readonly Action SetupAction = 6 | (options, configuration) => 7 | { 8 | var corsOptions = configuration.GetSection(CorsOptions.Cors).Get() ?? 9 | throw new InvalidOperationException("CORS options are not configured."); 10 | 11 | options.AddDefaultPolicy(policy => policy 12 | .SetIsOriginAllowed(origin => 13 | { 14 | if (corsOptions.AllowedOrigins.Any(o => string.Equals(o, origin, StringComparison.OrdinalIgnoreCase))) 15 | return true; 16 | 17 | if (corsOptions.StagingOriginPatterns is not null && 18 | origin.StartsWith(corsOptions.StagingOriginPatterns.Start, StringComparison.OrdinalIgnoreCase) && 19 | origin.EndsWith(corsOptions.StagingOriginPatterns.End, StringComparison.OrdinalIgnoreCase)) 20 | return true; 21 | 22 | return false; 23 | }) 24 | .WithMethods("GET") 25 | .AllowAnyHeader()); 26 | }; 27 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Api/Cors/CorsOptions.cs: -------------------------------------------------------------------------------- 1 | namespace FilterLists.Directory.Api.Cors; 2 | 3 | internal sealed record CorsOptions 4 | { 5 | public const string Cors = "Cors"; 6 | public string[] AllowedOrigins { get; init; } = []; 7 | public StagingOriginPatternsSettings? StagingOriginPatterns { get; init; } 8 | } 9 | 10 | /// 11 | /// Azure Static Web Apps staging environment origin patterns. 12 | /// 13 | internal sealed record StagingOriginPatternsSettings 14 | { 15 | public required string Start { get; init; } 16 | public required string End { get; init; } 17 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Api/Endpoints.cs: -------------------------------------------------------------------------------- 1 | using FilterLists.Directory.Application.Queries; 2 | using Microsoft.AspNetCore.Http.HttpResults; 3 | using Microsoft.OpenApi.Any; 4 | using Microsoft.OpenApi.Models; 5 | using static FilterLists.Directory.Api.OpenApi.OpenApiTags; 6 | 7 | namespace FilterLists.Directory.Api; 8 | 9 | internal static class Endpoints 10 | { 11 | private static readonly CacheControlFilter DefaultGetCacheFilter = new(3600, 86400); 12 | 13 | internal static void MapEndpoints(this WebApplication app) 14 | { 15 | app.MapGet("/languages", async (GetLanguages.Query query, CancellationToken ct) => await query.ExecuteAsync(ct)) 16 | .Produces>() 17 | .WithOpenApi(operation => new OpenApiOperation(operation) 18 | { 19 | Tags = [LanguagesTag], 20 | Summary = "Gets the languages targeted by the FilterLists.", 21 | OperationId = nameof(GetLanguages) 22 | }) 23 | .AddEndpointFilter(DefaultGetCacheFilter); 24 | 25 | app.MapGet("/licenses", async (GetLicenses.Query query, CancellationToken ct) => await query.ExecuteAsync(ct)) 26 | .Produces>() 27 | .WithOpenApi(operation => new OpenApiOperation(operation) 28 | { 29 | Tags = [LicensesTag], 30 | Summary = "Gets the licenses applied to the FilterLists.", 31 | OperationId = nameof(GetLicenses) 32 | }) 33 | .AddEndpointFilter(DefaultGetCacheFilter); 34 | 35 | app.MapGet("/lists", async (GetLists.Query query, CancellationToken ct) => await query.ExecuteAsync(ct)) 36 | .Produces>() 37 | .WithOpenApi(operation => new OpenApiOperation(operation) 38 | { 39 | Tags = [FilterListsTag], 40 | Summary = "Gets the FilterLists.", 41 | OperationId = nameof(GetLists) 42 | }) 43 | .AddEndpointFilter(DefaultGetCacheFilter); 44 | 45 | app.MapGet("/lists/{id:int}", 46 | async Task, NotFound>> 47 | (int id, GetListDetails.Query query, CancellationToken ct) => 48 | { 49 | var list = await query.ExecuteAsync(id, ct); 50 | return list is not null 51 | ? TypedResults.Ok(list) 52 | : TypedResults.NotFound(); 53 | }) 54 | .Produces() 55 | .Produces(StatusCodes.Status404NotFound) 56 | .WithOpenApi(operation => new OpenApiOperation(operation) 57 | { 58 | Tags = [FilterListsTag], 59 | Summary = "Gets the details of the FilterList.", 60 | OperationId = nameof(GetListDetails), 61 | Parameters = 62 | [ 63 | new OpenApiParameter 64 | { 65 | Name = "id", 66 | In = ParameterLocation.Path, 67 | Description = "The identifier of the FilterList.", 68 | Required = true, 69 | Example = new OpenApiInteger(1) 70 | } 71 | ] 72 | }) 73 | .AddEndpointFilter(DefaultGetCacheFilter); 74 | 75 | app.MapGet("/maintainers", 76 | async (GetMaintainers.Query query, CancellationToken ct) => await query.ExecuteAsync(ct)) 77 | .Produces>() 78 | .WithOpenApi(operation => new OpenApiOperation(operation) 79 | { 80 | Tags = [MaintainersTag], 81 | Summary = "Gets the maintainers of the FilterLists.", 82 | OperationId = nameof(GetMaintainers) 83 | }) 84 | .AddEndpointFilter(DefaultGetCacheFilter); 85 | 86 | app.MapGet("/software", async (GetSoftware.Query query, CancellationToken ct) => await query.ExecuteAsync(ct)) 87 | .Produces>() 88 | .WithOpenApi(operation => new OpenApiOperation(operation) 89 | { 90 | Tags = [SoftwareTag], 91 | Summary = "Gets the software that subscribes to the FilterLists.", 92 | OperationId = nameof(GetSoftware) 93 | }) 94 | .AddEndpointFilter(DefaultGetCacheFilter); 95 | 96 | app.MapGet("/syntaxes", async (GetSyntaxes.Query query, CancellationToken ct) => await query.ExecuteAsync(ct)) 97 | .Produces>() 98 | .WithOpenApi(operation => new OpenApiOperation(operation) 99 | { 100 | Tags = [SyntaxesTag], 101 | Summary = "Gets the syntaxes of the FilterLists.", 102 | OperationId = nameof(GetSyntaxes) 103 | }) 104 | .AddEndpointFilter(DefaultGetCacheFilter); 105 | 106 | app.MapGet("/tags", async (GetTags.Query query, CancellationToken ct) => await query.ExecuteAsync(ct)) 107 | .Produces>() 108 | .WithOpenApi(operation => new OpenApiOperation(operation) 109 | { 110 | Tags = [TagsTag], 111 | Summary = "Gets the tags of the FilterLists.", 112 | OperationId = nameof(GetTags) 113 | }) 114 | .AddEndpointFilter(DefaultGetCacheFilter); 115 | } 116 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Api/FilterLists.Directory.Api.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | 6 | 7 | 8 | true 9 | true 10 | mcr.microsoft.com/dotnet/runtime-deps:9.0-azurelinux3.0-distroless-extra 11 | linux-x64 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | all 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Api/OpenApi/OpenApiGenConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.OpenApi.Models; 2 | using Swashbuckle.AspNetCore.SwaggerGen; 3 | using ConfigurationExtensions = FilterLists.Directory.Application.ConfigurationExtensions; 4 | 5 | namespace FilterLists.Directory.Api.OpenApi; 6 | 7 | internal static class OpenApiGenConfiguration 8 | { 9 | internal static readonly Action SetupAction = options => 10 | { 11 | options.SwaggerDoc( 12 | "v1", 13 | new OpenApiInfo 14 | { 15 | Title = "FilterLists Directory API", 16 | Description = "An ASP.NET Core API serving the core FilterList information.", 17 | Version = "v1", 18 | TermsOfService = 19 | new Uri("https://github.com/collinbarrett/FilterLists/blob/main/.github/CODE_OF_CONDUCT.md"), 20 | Contact = new OpenApiContact { Name = "FilterLists", Url = new Uri("https://filterlists.com") }, 21 | License = new OpenApiLicense 22 | { 23 | Name = "MIT License", 24 | Url = new Uri("https://github.com/collinbarrett/FilterLists/blob/main/LICENSE") 25 | } 26 | }); 27 | 28 | // include view model xml comments 29 | var xmlFilename = $"{typeof(ConfigurationExtensions).Assembly.GetName().Name}.xml"; 30 | options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); 31 | 32 | // include OpenApiTag Description and ExternalDocs 33 | options.DocumentFilter(); 34 | 35 | // allow re-using simple type names like "Response" 36 | options.CustomSchemaIds(s => s.FullName?.Replace("+", ".", StringComparison.InvariantCultureIgnoreCase)); 37 | }; 38 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Api/OpenApi/OpenApiTags.cs: -------------------------------------------------------------------------------- 1 | using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; 2 | using FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; 3 | using Microsoft.OpenApi.Models; 4 | using Swashbuckle.AspNetCore.SwaggerGen; 5 | 6 | namespace FilterLists.Directory.Api.OpenApi; 7 | 8 | internal static class OpenApiTags 9 | { 10 | internal static readonly OpenApiTag LanguagesTag = new() 11 | { 12 | Name = nameof(QueryDbContext.Languages), 13 | Description = "A written form of communication used by sites targeted by a FilterList", 14 | ExternalDocs = new OpenApiExternalDocs 15 | { 16 | Description = $"{nameof(FilterLists)} {nameof(Language)} Wiki", 17 | Url = new Uri($"https://github.com/collinbarrett/FilterLists/wiki/{nameof(Language)}") 18 | } 19 | }; 20 | 21 | internal static readonly OpenApiTag LicensesTag = new() 22 | { 23 | Name = nameof(QueryDbContext.Licenses), 24 | Description = "A legal document governing the use or redistribution of a FilterList", 25 | ExternalDocs = new OpenApiExternalDocs 26 | { 27 | Description = $"{nameof(FilterLists)} {nameof(License)} Wiki", 28 | Url = new Uri($"https://github.com/collinbarrett/FilterLists/wiki/{nameof(License)}") 29 | } 30 | }; 31 | 32 | internal static readonly OpenApiTag FilterListsTag = new() 33 | { 34 | Name = nameof(QueryDbContext.FilterLists), 35 | Description = "A text file containing a list of rules for blocking or manipulating internet traffic", 36 | ExternalDocs = new OpenApiExternalDocs 37 | { 38 | Description = $"{nameof(FilterLists)} {nameof(FilterList)} Wiki", 39 | Url = new Uri($"https://github.com/collinbarrett/FilterLists/wiki/{nameof(FilterList)}") 40 | } 41 | }; 42 | 43 | internal static readonly OpenApiTag MaintainersTag = new() 44 | { 45 | Name = nameof(QueryDbContext.Maintainers), 46 | Description = "An individual, group, or organization who maintains one or more FilterLists", 47 | ExternalDocs = new OpenApiExternalDocs 48 | { 49 | Description = $"{nameof(FilterLists)} {nameof(Maintainer)} Wiki", 50 | Url = new Uri($"https://github.com/collinbarrett/FilterLists/wiki/{nameof(Maintainer)}") 51 | } 52 | }; 53 | 54 | internal static readonly OpenApiTag SoftwareTag = new() 55 | { 56 | Name = nameof(QueryDbContext.Software), 57 | Description = "An application, browser extension, or other utility that consumes FilterLists", 58 | ExternalDocs = new OpenApiExternalDocs 59 | { 60 | Description = $"{nameof(FilterLists)} {nameof(Software)} Wiki", 61 | Url = new Uri($"https://github.com/collinbarrett/FilterLists/wiki/{nameof(Software)}") 62 | } 63 | }; 64 | 65 | internal static readonly OpenApiTag SyntaxesTag = new() 66 | { 67 | Name = nameof(QueryDbContext.Syntaxes), 68 | Description = "A named set of rules that govern the format of a FilterList", 69 | ExternalDocs = new OpenApiExternalDocs 70 | { 71 | Description = $"{nameof(FilterLists)} {nameof(Syntax)} Wiki", 72 | Url = new Uri($"https://github.com/collinbarrett/FilterLists/wiki/{nameof(Syntax)}") 73 | } 74 | }; 75 | 76 | internal static readonly OpenApiTag TagsTag = new() 77 | { 78 | Name = nameof(QueryDbContext.Tags), 79 | Description = 80 | "A generic taxonomy applied to a FilterList to provide information about its contents and/or purpose", 81 | ExternalDocs = new OpenApiExternalDocs 82 | { 83 | Description = $"{nameof(FilterLists)} {nameof(Tag)} Wiki", 84 | Url = new Uri($"https://github.com/collinbarrett/FilterLists/wiki/{nameof(Tag)}") 85 | } 86 | }; 87 | 88 | internal sealed class TagDescriptionsDocumentFilter : IDocumentFilter 89 | { 90 | public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) 91 | { 92 | swaggerDoc.Tags = 93 | [ 94 | LanguagesTag, 95 | LicensesTag, 96 | FilterListsTag, 97 | MaintainersTag, 98 | SoftwareTag, 99 | SyntaxesTag, 100 | TagsTag 101 | ]; 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Api/OpenApi/SwaggerUiConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.SwaggerUI; 2 | 3 | namespace FilterLists.Directory.Api.OpenApi; 4 | 5 | internal static class SwaggerUiConfiguration 6 | { 7 | internal static readonly Action SetupAction = options => 8 | { 9 | options.DocumentTitle = "FilterLists API"; 10 | options.SwaggerEndpoint("/v1/openapi.json", "FilterLists Directory API v1"); 11 | options.RoutePrefix = string.Empty; 12 | }; 13 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Api/Program.cs: -------------------------------------------------------------------------------- 1 | using FilterLists.Directory.Api; 2 | using FilterLists.Directory.Api.Cors; 3 | using FilterLists.Directory.Api.OpenApi; 4 | using FilterLists.Directory.Application; 5 | 6 | var builder = WebApplication.CreateBuilder(args); 7 | 8 | builder.WebHost.ConfigureKestrel(serverOptions => serverOptions.AddServerHeader = false); 9 | builder.AddServiceDefaults(); 10 | builder.Services.AddCors(options => CorsConfiguration.SetupAction(options, builder.Configuration)); 11 | builder.Services.AddProblemDetails(); 12 | builder.Services.AddEndpointsApiExplorer(); 13 | builder.Services.AddSwaggerGen(OpenApiGenConfiguration.SetupAction); 14 | builder.AddApplication(); 15 | 16 | var app = builder.Build(); 17 | 18 | app.UseExceptionHandler(); 19 | app.UseCors(); 20 | app.MapDefaultEndpoints(); 21 | app.MapEndpoints(); 22 | app.UseSwagger(o => o.RouteTemplate = "{documentName}/openapi.json"); 23 | app.UseSwaggerUI(SwaggerUiConfiguration.SetupAction); 24 | 25 | app.Run(); -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Api/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "http": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": true, 8 | "launchUrl": "index.html", 9 | "applicationUrl": "http://localhost:5444", 10 | "environmentVariables": { 11 | "ASPNETCORE_ENVIRONMENT": "Development" 12 | } 13 | }, 14 | "https": { 15 | "commandName": "Project", 16 | "dotnetRunMessages": true, 17 | "launchBrowser": true, 18 | "launchUrl": "index.html", 19 | "applicationUrl": "https://localhost:7490;http://localhost:5444", 20 | "environmentVariables": { 21 | "ASPNETCORE_ENVIRONMENT": "Development" 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Api/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "Cors": { 9 | "AllowedOrigins": [ 10 | "http://localhost:3000" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "Cors": { 10 | "AllowedOrigins": [ 11 | "https://filterlists.com" 12 | ], 13 | "StagingOriginPatterns": { 14 | "Start": "https://nice-water-05873140f-", 15 | "End": ".eastus2.5.azurestaticapps.net" 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Application/ConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | using FilterLists.Directory.Application.Queries; 2 | using FilterLists.Directory.Infrastructure; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Hosting; 5 | 6 | namespace FilterLists.Directory.Application; 7 | 8 | public static class ConfigurationExtensions 9 | { 10 | public static void AddApplication(this IHostApplicationBuilder builder) 11 | { 12 | builder.AddInfrastructure(); 13 | builder.Services.AddScoped(); 14 | builder.Services.AddScoped(); 15 | builder.Services.AddScoped(); 16 | builder.Services.AddScoped(); 17 | builder.Services.AddScoped(); 18 | builder.Services.AddScoped(); 19 | builder.Services.AddScoped(); 20 | builder.Services.AddScoped(); 21 | } 22 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Application/FilterLists.Directory.Application.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | true 6 | $(NoWarn);1591 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Application/Queries/GetLanguages.cs: -------------------------------------------------------------------------------- 1 | using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.Extensions.Caching.Hybrid; 4 | 5 | namespace FilterLists.Directory.Application.Queries; 6 | 7 | public static class GetLanguages 8 | { 9 | public class Query(QueryDbContext ctx, HybridCache cache) 10 | { 11 | public async Task> ExecuteAsync(CancellationToken ct) 12 | { 13 | const string key = nameof(GetLanguages); 14 | return await cache.GetOrCreateAsync( 15 | key, 16 | async cancel => 17 | await ctx.Languages 18 | .Where(l => l.FilterListLanguages.Any()) 19 | .OrderBy(l => l.Iso6391) 20 | .Select(l => new Response( 21 | l.Id, 22 | l.Iso6391, 23 | l.Name, 24 | l.FilterListLanguages 25 | .OrderBy(fl => fl.FilterListId) 26 | .Select(fl => fl.FilterListId) 27 | )) 28 | .TagWith(key) 29 | .ToListAsync(cancel), 30 | cancellationToken: ct); 31 | } 32 | } 33 | 34 | /// The identifier. 35 | /// The unique ISO 639-1 code. 36 | /// The unique ISO name. 37 | /// The identifiers of the FilterLists targeted by this Language. 38 | public sealed record Response(short Id, string Iso6391, string Name, IEnumerable FilterListIds); 39 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Application/Queries/GetLicenses.cs: -------------------------------------------------------------------------------- 1 | using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.Extensions.Caching.Hybrid; 4 | 5 | namespace FilterLists.Directory.Application.Queries; 6 | 7 | public static class GetLicenses 8 | { 9 | public class Query(QueryDbContext ctx, HybridCache cache) 10 | { 11 | public async Task> ExecuteAsync(CancellationToken ct) 12 | { 13 | const string key = nameof(GetLicenses); 14 | return await cache.GetOrCreateAsync( 15 | key, 16 | async cancel => 17 | await ctx.Licenses 18 | .Where(l => l.FilterLists.Any()) 19 | .OrderBy(l => l.Id) 20 | .Select(l => new Response( 21 | l.Id, 22 | l.Name, 23 | l.Url, 24 | l.PermitsModification, 25 | l.PermitsDistribution, 26 | l.PermitsCommercialUse, 27 | l.FilterLists 28 | .OrderBy(f => f.Id) 29 | .Select(f => f.Id) 30 | )) 31 | .TagWith(key) 32 | .ToListAsync(cancel), 33 | cancellationToken: ct); 34 | } 35 | } 36 | 37 | /// The identifier. 38 | /// The unique name. 39 | /// The URL of the home page. 40 | /// If the License permits modification. 41 | /// If the License permits distribution. 42 | /// If the License permits commercial use. 43 | /// The identifiers of the FilterLists released under this License. 44 | public sealed record Response( 45 | int Id, 46 | string Name, 47 | Uri? Url, 48 | bool PermitsModification, 49 | bool PermitsDistribution, 50 | bool PermitsCommercialUse, 51 | IEnumerable FilterListIds); 52 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Application/Queries/GetLists.cs: -------------------------------------------------------------------------------- 1 | using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.Extensions.Caching.Hybrid; 4 | 5 | namespace FilterLists.Directory.Application.Queries; 6 | 7 | public static class GetLists 8 | { 9 | public class Query(QueryDbContext ctx, HybridCache cache) 10 | { 11 | public async Task> ExecuteAsync(CancellationToken ct) 12 | { 13 | const string key = nameof(GetLists); 14 | return await cache.GetOrCreateAsync( 15 | key, 16 | async cancel => 17 | await ctx.FilterLists 18 | .OrderBy(f => f.Id) 19 | .Select(f => new Response 20 | ( 21 | f.Id, 22 | f.Name, 23 | f.Description, 24 | f.LicenseId, 25 | f.FilterListSyntaxes 26 | .OrderBy(fs => fs.SyntaxId) 27 | .Select(fs => fs.SyntaxId), 28 | f.FilterListLanguages 29 | .OrderBy(fl => fl.LanguageId) 30 | .Select(fl => fl.LanguageId), 31 | f.FilterListTags 32 | .OrderBy(ft => ft.TagId) 33 | .Select(ft => ft.TagId), 34 | f.ViewUrls 35 | .OrderBy(u => u.SegmentNumber) 36 | .ThenBy(u => u.Primariness) 37 | .Select(u => u.Url) 38 | .FirstOrDefault(), 39 | f.FilterListMaintainers 40 | .OrderBy(fm => fm.MaintainerId) 41 | .Select(fm => fm.MaintainerId) 42 | )) 43 | .TagWith(key) 44 | .ToListAsync(cancel), 45 | cancellationToken: ct); 46 | } 47 | } 48 | 49 | /// The identifier. 50 | /// The unique name in title case. 51 | /// 53 | /// The brief description in English (preferably quoted from the project). 54 | /// 55 | /// The identifier of the License under which this FilterList is released. 56 | /// The identifiers of the Syntaxes implemented by this FilterList. 57 | /// The identifiers of the Languages targeted by this FilterList. 58 | /// The identifiers of the Tags applied to this FilterList. 59 | /// The primary view URL. 60 | /// The identifiers of the Maintainers of this FilterList. 61 | public sealed record Response( 62 | int Id, 63 | string Name, 64 | string? Description, 65 | int LicenseId, 66 | IEnumerable SyntaxIds, 67 | IEnumerable LanguageIds, 68 | IEnumerable TagIds, 69 | Uri? PrimaryViewUrl, 70 | IEnumerable MaintainerIds); 71 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Application/Queries/GetMaintainers.cs: -------------------------------------------------------------------------------- 1 | using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.Extensions.Caching.Hybrid; 4 | 5 | namespace FilterLists.Directory.Application.Queries; 6 | 7 | public static class GetMaintainers 8 | { 9 | public class Query(QueryDbContext ctx, HybridCache cache) 10 | { 11 | public async Task> ExecuteAsync(CancellationToken ct) 12 | { 13 | const string key = nameof(GetMaintainers); 14 | return await cache.GetOrCreateAsync( 15 | key, 16 | async cancel => 17 | await ctx.Maintainers 18 | .Where(m => m.FilterListMaintainers.Any()) 19 | .OrderBy(m => m.Id) 20 | .Select(m => new Response( 21 | m.Id, 22 | m.Name, 23 | m.Url, 24 | m.EmailAddress, 25 | m.TwitterHandle, 26 | m.FilterListMaintainers 27 | .OrderBy(fm => fm.FilterListId) 28 | .Select(fm => fm.FilterListId) 29 | )) 30 | .TagWith(key) 31 | .ToListAsync(cancel), 32 | cancellationToken: ct); 33 | } 34 | } 35 | 36 | /// The identifier. 37 | /// The unique name. 38 | /// The URL of the home page. 39 | /// The email address. 40 | /// The Twitter handle. 41 | /// The identifiers of the FilterLists maintained by this Maintainer. 42 | public sealed record Response( 43 | int Id, 44 | string Name, 45 | Uri? Url, 46 | string? EmailAddress, 47 | string? TwitterHandle, 48 | IEnumerable FilterListIds); 49 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Application/Queries/GetSoftware.cs: -------------------------------------------------------------------------------- 1 | using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.Extensions.Caching.Hybrid; 4 | 5 | namespace FilterLists.Directory.Application.Queries; 6 | 7 | public static class GetSoftware 8 | { 9 | public class Query(QueryDbContext ctx, HybridCache cache) 10 | { 11 | public async Task> ExecuteAsync(CancellationToken ct) 12 | { 13 | const string key = nameof(GetSoftware); 14 | return await cache.GetOrCreateAsync( 15 | key, 16 | async cancel => 17 | await ctx.Software 18 | .Where(s => s.SoftwareSyntaxes.Any(ss => ss.Syntax.FilterListSyntaxes.Any())) 19 | .OrderBy(s => s.Id) 20 | .Select(s => new Response 21 | ( 22 | s.Id, 23 | s.Name, 24 | s.Description, 25 | s.HomeUrl, 26 | s.DownloadUrl, 27 | s.SupportsAbpUrlScheme, 28 | s.SoftwareSyntaxes 29 | .OrderBy(ss => ss.SyntaxId) 30 | .Select(ss => ss.SyntaxId) 31 | )) 32 | .TagWith(key) 33 | .ToListAsync(cancel), 34 | cancellationToken: ct); 35 | } 36 | } 37 | 38 | /// The identifier. 39 | /// The unique name. 40 | /// 42 | /// The description. 43 | /// 44 | /// The URL of the home page. 45 | /// The URL of the Software download. 46 | /// 47 | /// If the Software supports the abp: URL scheme to click-to-subscribe to a FilterList. 48 | /// 49 | /// The identifiers of the Syntaxes that this Software supports. 50 | public sealed record Response( 51 | long Id, 52 | string Name, 53 | string? Description, 54 | Uri? HomeUrl, 55 | Uri? DownloadUrl, 56 | bool SupportsAbpUrlScheme, 57 | IEnumerable SyntaxIds); 58 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Application/Queries/GetSyntaxes.cs: -------------------------------------------------------------------------------- 1 | using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.Extensions.Caching.Hybrid; 4 | 5 | namespace FilterLists.Directory.Application.Queries; 6 | 7 | public static class GetSyntaxes 8 | { 9 | public class Query(QueryDbContext ctx, HybridCache cache) 10 | { 11 | public async Task> ExecuteAsync(CancellationToken ct) 12 | { 13 | const string key = nameof(GetSyntaxes); 14 | return await cache.GetOrCreateAsync( 15 | key, 16 | async cancel => 17 | await ctx.Syntaxes 18 | .Where(s => s.FilterListSyntaxes.Any()) 19 | .OrderBy(s => s.Id) 20 | .Select(s => new Response 21 | ( 22 | s.Id, 23 | s.Name, 24 | s.Description, 25 | s.Url, 26 | s.FilterListSyntaxes 27 | .OrderBy(fs => fs.FilterListId) 28 | .Select(fs => fs.FilterListId), 29 | s.SoftwareSyntaxes 30 | .OrderBy(ss => ss.SoftwareId) 31 | .Select(ss => ss.SoftwareId) 32 | )) 33 | .TagWith(key) 34 | .ToListAsync(cancel), 35 | cancellationToken: ct); 36 | } 37 | } 38 | 39 | /// The identifier. 40 | /// The unique name. 41 | /// The description. 42 | /// The URL of the home page. 43 | /// The identifiers of the FilterLists implementing this Syntax. 44 | /// The identifiers of the Software that supports this Syntax. 45 | public sealed record Response( 46 | short Id, 47 | string Name, 48 | string? Description, 49 | Uri? Url, 50 | IEnumerable FilterListIds, 51 | IEnumerable SoftwareIds); 52 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Application/Queries/GetTags.cs: -------------------------------------------------------------------------------- 1 | using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.Extensions.Caching.Hybrid; 4 | 5 | namespace FilterLists.Directory.Application.Queries; 6 | 7 | public static class GetTags 8 | { 9 | public class Query(QueryDbContext ctx, HybridCache cache) 10 | { 11 | public async Task> ExecuteAsync(CancellationToken ct) 12 | { 13 | const string key = nameof(GetTags); 14 | return await cache.GetOrCreateAsync( 15 | key, 16 | async cancel => 17 | await ctx.Tags 18 | .Where(t => t.FilterListTags.Any()) 19 | .OrderBy(t => t.Id) 20 | .Select(t => new Response 21 | ( 22 | t.Id, 23 | t.Name, 24 | t.Description, 25 | t.FilterListTags 26 | .OrderBy(flt => flt.FilterListId) 27 | .Select(flt => flt.FilterListId) 28 | )) 29 | .TagWith(key) 30 | .ToListAsync(cancel), 31 | cancellationToken: ct); 32 | } 33 | } 34 | 35 | /// The identifier. 36 | /// The unique name. 37 | /// The description. 38 | /// The identifiers of the FilterLists to which this Tag is applied. 39 | public sealed record Response(int Id, string Name, string? Description, IEnumerable FilterListIds); 40 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure.Migrations/FilterLists.Directory.Infrastructure.Migrations.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | $(NoWarn);CA1707;CA1861 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure.Migrations/Migrations/20250508015706_4807.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace FilterLists.Directory.Infrastructure.Migrations.Migrations 6 | { 7 | /// 8 | public partial class _4807 : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.InsertData( 14 | table: "FilterList", 15 | columns: new[] { "Id", "ChatUrl", "Description", "DonateUrl", "EmailAddress", "ForumUrl", "HomeUrl", "IssuesUrl", "LicenseId", "Name", "OnionUrl", "PolicyUrl", "SubmissionUrl" }, 16 | values: new object[] { 2737, null, "A blocklist of Bolloré, alt-right and french fascist web sites.", null, null, null, "https://bloquebollore.codeberg.page/desarmons-bollore-et-l-extreme-droite.txt", "https://codeberg.org/BloqueBollore/pages/issues", 43, "French far-right blocklist", null, null, null }); 17 | 18 | migrationBuilder.InsertData( 19 | table: "Maintainer", 20 | columns: new[] { "Id", "EmailAddress", "Name", "TwitterHandle", "Url" }, 21 | values: new object[] { 199, null, "BloqueBollore", null, "https://codeberg.org/BloqueBollore/" }); 22 | 23 | migrationBuilder.InsertData( 24 | table: "FilterListLanguage", 25 | columns: new[] { "FilterListId", "LanguageId" }, 26 | values: new object[] { 2737, (short)47 }); 27 | 28 | migrationBuilder.InsertData( 29 | table: "FilterListMaintainer", 30 | columns: new[] { "FilterListId", "MaintainerId" }, 31 | values: new object[] { 2737, 199 }); 32 | 33 | migrationBuilder.InsertData( 34 | table: "FilterListTag", 35 | columns: new[] { "FilterListId", "TagId" }, 36 | values: new object[] { 2737, 25 }); 37 | 38 | migrationBuilder.InsertData( 39 | table: "FilterListViewUrl", 40 | columns: new[] { "FilterListId", "Id", "Primariness", "Url" }, 41 | values: new object[] { 2737, 3069, (short)1, "https://codeberg.org/BloqueBollore/pages/src/branch/main/desarmons-bollore-et-l-extreme-droite.txt" }); 42 | } 43 | 44 | /// 45 | protected override void Down(MigrationBuilder migrationBuilder) 46 | { 47 | migrationBuilder.DeleteData( 48 | table: "FilterListLanguage", 49 | keyColumns: new[] { "FilterListId", "LanguageId" }, 50 | keyValues: new object[] { 2737, (short)47 }); 51 | 52 | migrationBuilder.DeleteData( 53 | table: "FilterListMaintainer", 54 | keyColumns: new[] { "FilterListId", "MaintainerId" }, 55 | keyValues: new object[] { 2737, 199 }); 56 | 57 | migrationBuilder.DeleteData( 58 | table: "FilterListTag", 59 | keyColumns: new[] { "FilterListId", "TagId" }, 60 | keyValues: new object[] { 2737, 25 }); 61 | 62 | migrationBuilder.DeleteData( 63 | table: "FilterListViewUrl", 64 | keyColumns: new[] { "FilterListId", "Id" }, 65 | keyValues: new object[] { 2737, 3069 }); 66 | 67 | migrationBuilder.DeleteData( 68 | table: "FilterList", 69 | keyColumn: "Id", 70 | keyValue: 2737); 71 | 72 | migrationBuilder.DeleteData( 73 | table: "Maintainer", 74 | keyColumn: "Id", 75 | keyValue: 199); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure.Migrations/Migrations/20250510182832_4823.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace FilterLists.Directory.Infrastructure.Migrations.Migrations 6 | { 7 | /// 8 | public partial class _4823 : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.UpdateData( 14 | table: "FilterList", 15 | keyColumn: "Id", 16 | keyValue: 2737, 17 | column: "HomeUrl", 18 | value: "https://codeberg.org/BloqueBollore/pages/src/branch/main/desarmons-bollore-et-l-extreme-droite.txt"); 19 | 20 | migrationBuilder.UpdateData( 21 | table: "FilterListViewUrl", 22 | keyColumns: new[] { "FilterListId", "Id" }, 23 | keyValues: new object[] { 2737, 3069 }, 24 | column: "Url", 25 | value: "https://bloquebollore.codeberg.page/desarmons-bollore-et-l-extreme-droite.txt"); 26 | } 27 | 28 | /// 29 | protected override void Down(MigrationBuilder migrationBuilder) 30 | { 31 | migrationBuilder.UpdateData( 32 | table: "FilterList", 33 | keyColumn: "Id", 34 | keyValue: 2737, 35 | column: "HomeUrl", 36 | value: "https://bloquebollore.codeberg.page/desarmons-bollore-et-l-extreme-droite.txt"); 37 | 38 | migrationBuilder.UpdateData( 39 | table: "FilterListViewUrl", 40 | keyColumns: new[] { "FilterListId", "Id" }, 41 | keyValues: new object[] { 2737, 3069 }, 42 | column: "Url", 43 | value: "https://codeberg.org/BloqueBollore/pages/src/branch/main/desarmons-bollore-et-l-extreme-droite.txt"); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/ConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | using FilterLists.Directory.Infrastructure.Persistence.Queries; 2 | using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace FilterLists.Directory.Infrastructure; 8 | 9 | public static class ConfigurationExtensions 10 | { 11 | private const string MigrationsAssembly = "FilterLists.Directory.Infrastructure.Migrations"; 12 | 13 | public static void AddInfrastructure(this IHostApplicationBuilder builder) 14 | { 15 | // TODO: use different connection strings for migrations and queries (https://stackoverflow.com/q/78564037/2343739) 16 | builder.AddSqlServerDbContext("directorydb", 17 | _ => { }, 18 | o => o.UseSqlServer(so => 19 | // retry on Microsoft.Data.SqlClient.SqlException (0x80131904): A connection was successfully established with the server, but then an error occurred during the pre-login handshake. (provider: TCP Provider, error: 0 - Undefined error: 0) 20 | so.EnableRetryOnFailure([0]) 21 | .MigrationsAssembly(MigrationsAssembly)) 22 | .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) 23 | .EnableSensitiveDataLogging(string.Equals( 24 | Environment.GetEnvironmentVariable("DOTNET_RUNNING_EF_CORE_TOOLS"), "true", 25 | StringComparison.OrdinalIgnoreCase))); 26 | 27 | builder.Services.AddHostedService(); 28 | builder.Services.AddHybridCache(); 29 | } 30 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/FilterLists.Directory.Infrastructure.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/QueryDbContext.cs: -------------------------------------------------------------------------------- 1 | using FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Context; 5 | 6 | public sealed class QueryDbContext(DbContextOptions options) : DbContext(options) 7 | { 8 | private const string ReadOnlyErrorMessage = "This context is read-only and cannot save changes."; 9 | 10 | public IQueryable FilterLists => Set(); 11 | public IQueryable Languages => Set(); 12 | public IQueryable Licenses => Set(); 13 | public IQueryable Maintainers => Set(); 14 | public IQueryable Software => Set(); 15 | public IQueryable Syntaxes => Set(); 16 | public IQueryable Tags => Set(); 17 | 18 | protected override void OnModelCreating(ModelBuilder modelBuilder) 19 | { 20 | base.OnModelCreating(modelBuilder); 21 | 22 | modelBuilder.UseCollation("Latin1_General_100_CI_AS_SC"); 23 | modelBuilder.ApplyConfigurationsFromAssembly( 24 | GetType().Assembly, 25 | type => type.Namespace == typeof(FilterListTypeConfiguration).Namespace); 26 | } 27 | 28 | public override int SaveChanges() 29 | { 30 | throw new InvalidOperationException(ReadOnlyErrorMessage); 31 | } 32 | 33 | public override int SaveChanges(bool acceptAllChangesOnSuccess) 34 | { 35 | throw new InvalidOperationException(ReadOnlyErrorMessage); 36 | } 37 | 38 | public override Task SaveChangesAsync(CancellationToken cancellationToken = default) 39 | { 40 | throw new InvalidOperationException(ReadOnlyErrorMessage); 41 | } 42 | 43 | public override Task SaveChangesAsync(bool acceptAllChangesOnSuccess, 44 | CancellationToken cancellationToken = default) 45 | { 46 | throw new InvalidOperationException(ReadOnlyErrorMessage); 47 | } 48 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Dependent.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | 4 | namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; 5 | 6 | public sealed record Dependent 7 | { 8 | public int DependencyFilterListId { get; init; } 9 | public FilterList DependencyFilterList { get; init; } = null!; 10 | public int DependentFilterListId { get; init; } 11 | public FilterList DependentFilterList { get; init; } = null!; 12 | } 13 | 14 | internal sealed class DependentTypeConfiguration : IEntityTypeConfiguration 15 | { 16 | public void Configure(EntityTypeBuilder builder) 17 | { 18 | builder.HasKey(d => new { d.DependencyFilterListId, d.DependentFilterListId }); 19 | builder.HasOne(d => d.DependencyFilterList) 20 | .WithMany(fl => fl.DependentFilterLists) 21 | .HasForeignKey(d => d.DependencyFilterListId); 22 | builder.HasOne(d => d.DependentFilterList) 23 | .WithMany(fl => fl.DependencyFilterLists) 24 | .HasForeignKey(d => d.DependentFilterListId) 25 | .OnDelete(DeleteBehavior.NoAction); 26 | builder.HasDataJsonFile(); 27 | } 28 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterList.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | 4 | namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; 5 | 6 | public sealed record FilterList 7 | { 8 | public int Id { get; init; } 9 | public string Name { get; init; } = null!; 10 | public string? Description { get; init; } 11 | public int LicenseId { get; init; } 12 | public License License { get; init; } = null!; 13 | public IEnumerable FilterListSyntaxes { get; init; } = new List(); 14 | public IEnumerable FilterListLanguages { get; init; } = new List(); 15 | public IEnumerable FilterListTags { get; init; } = new List(); 16 | public IEnumerable ViewUrls { get; init; } = new List(); 17 | public Uri? HomeUrl { get; init; } 18 | public Uri? OnionUrl { get; init; } 19 | public Uri? PolicyUrl { get; init; } 20 | public Uri? SubmissionUrl { get; init; } 21 | public Uri? IssuesUrl { get; init; } 22 | public Uri? ForumUrl { get; init; } 23 | public Uri? ChatUrl { get; init; } 24 | public string? EmailAddress { get; init; } 25 | public Uri? DonateUrl { get; init; } 26 | public IEnumerable FilterListMaintainers { get; init; } = new List(); 27 | public IEnumerable UpstreamFilterLists { get; init; } = new List(); 28 | public IEnumerable ForkFilterLists { get; init; } = new List(); 29 | public IEnumerable IncludedInFilterLists { get; init; } = new List(); 30 | public IEnumerable IncludesFilterLists { get; init; } = new List(); 31 | public IEnumerable DependencyFilterLists { get; init; } = new List(); 32 | public IEnumerable DependentFilterLists { get; init; } = new List(); 33 | } 34 | 35 | internal sealed class FilterListTypeConfiguration : IEntityTypeConfiguration 36 | { 37 | public void Configure(EntityTypeBuilder builder) 38 | { 39 | builder.Property(f => f.Name) 40 | .HasMaxLength(256); 41 | builder.HasIndex(f => f.Name) 42 | .IsUnique(); 43 | builder.Property(f => f.LicenseId) 44 | .HasDefaultValue(5); 45 | builder.HasOne(f => f.License) 46 | .WithMany(l => l.FilterLists) 47 | .OnDelete(DeleteBehavior.Restrict); 48 | builder.Property(f => f.HomeUrl) 49 | .HasMaxLength(512); 50 | builder.Property(f => f.OnionUrl) 51 | .HasMaxLength(512); 52 | builder.Property(f => f.PolicyUrl) 53 | .HasMaxLength(512); 54 | builder.Property(f => f.SubmissionUrl) 55 | .HasMaxLength(512); 56 | builder.Property(f => f.IssuesUrl) 57 | .HasMaxLength(512); 58 | builder.Property(f => f.ForumUrl) 59 | .HasMaxLength(512); 60 | builder.Property(f => f.ChatUrl) 61 | .HasMaxLength(512); 62 | builder.Property(f => f.EmailAddress) 63 | .HasMaxLength(256); 64 | builder.Property(f => f.DonateUrl) 65 | .HasMaxLength(512); 66 | builder.OwnsMany( 67 | f => f.ViewUrls, 68 | b => 69 | { 70 | b.Property(u => u.SegmentNumber) 71 | .HasDefaultValue(1); 72 | b.Property(u => u.Primariness) 73 | .HasDefaultValue(1); 74 | b.Property(u => u.Url) 75 | .HasMaxLength(512); 76 | b.HasIndex(u => new { u.FilterListId, u.SegmentNumber, u.Primariness }) 77 | .IsUnique(); 78 | b.HasDataJsonFile(); 79 | }); 80 | builder.HasDataJsonFile(); 81 | } 82 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListLanguage.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | 4 | namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; 5 | 6 | public sealed record FilterListLanguage 7 | { 8 | public int FilterListId { get; init; } 9 | public FilterList FilterList { get; init; } = null!; 10 | public short LanguageId { get; init; } 11 | public Language Language { get; init; } = null!; 12 | } 13 | 14 | internal sealed class FilterListLanguageTypeConfiguration : IEntityTypeConfiguration 15 | { 16 | public void Configure(EntityTypeBuilder builder) 17 | { 18 | builder.HasKey(fll => new { fll.FilterListId, fll.LanguageId }); 19 | builder.HasDataJsonFile(); 20 | } 21 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListMaintainer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | 4 | namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; 5 | 6 | public sealed record FilterListMaintainer 7 | { 8 | public int FilterListId { get; init; } 9 | public FilterList FilterList { get; init; } = null!; 10 | public int MaintainerId { get; init; } 11 | public Maintainer Maintainer { get; init; } = null!; 12 | } 13 | 14 | internal sealed class FilterListMaintainerTypeConfiguration : IEntityTypeConfiguration 15 | { 16 | public void Configure(EntityTypeBuilder builder) 17 | { 18 | builder.HasKey(flm => new { flm.FilterListId, flm.MaintainerId }); 19 | builder.HasDataJsonFile(); 20 | } 21 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListSyntax.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | 4 | namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; 5 | 6 | public sealed record FilterListSyntax 7 | { 8 | public int FilterListId { get; init; } 9 | public FilterList FilterList { get; init; } = null!; 10 | public short SyntaxId { get; init; } 11 | public Syntax Syntax { get; init; } = null!; 12 | } 13 | 14 | internal sealed class FilterListSyntaxTypeConfiguration : IEntityTypeConfiguration 15 | { 16 | public void Configure(EntityTypeBuilder builder) 17 | { 18 | builder.HasKey(fls => new { fls.FilterListId, fls.SyntaxId }); 19 | builder.HasDataJsonFile(); 20 | } 21 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListTag.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | 4 | namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; 5 | 6 | public sealed record FilterListTag 7 | { 8 | public int FilterListId { get; init; } 9 | public FilterList FilterList { get; init; } = null!; 10 | public int TagId { get; init; } 11 | public Tag Tag { get; init; } = null!; 12 | } 13 | 14 | internal sealed class FilterListTagTypeConfiguration : IEntityTypeConfiguration 15 | { 16 | public void Configure(EntityTypeBuilder builder) 17 | { 18 | builder.HasKey(flt => new { flt.FilterListId, flt.TagId }); 19 | builder.HasDataJsonFile(); 20 | } 21 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/FilterListViewUrl.cs: -------------------------------------------------------------------------------- 1 | namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; 2 | 3 | public sealed record FilterListViewUrl 4 | { 5 | public int Id { get; init; } 6 | public int FilterListId { get; init; } 7 | public FilterList FilterList { get; init; } = null!; 8 | public short SegmentNumber { get; init; } 9 | public short Primariness { get; init; } 10 | public Uri Url { get; init; } = null!; 11 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Fork.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | 4 | namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; 5 | 6 | public sealed record Fork 7 | { 8 | public int UpstreamFilterListId { get; init; } 9 | public FilterList UpstreamFilterList { get; init; } = null!; 10 | public int ForkFilterListId { get; init; } 11 | public FilterList ForkFilterList { get; init; } = null!; 12 | } 13 | 14 | internal sealed class ForkTypeConfiguration : IEntityTypeConfiguration 15 | { 16 | public void Configure(EntityTypeBuilder builder) 17 | { 18 | builder.HasKey(f => new { f.UpstreamFilterListId, f.ForkFilterListId }); 19 | builder.HasOne(f => f.UpstreamFilterList) 20 | .WithMany(fl => fl.ForkFilterLists) 21 | .HasForeignKey(f => f.UpstreamFilterListId); 22 | builder.HasOne(f => f.ForkFilterList) 23 | .WithMany(fl => fl.UpstreamFilterLists) 24 | .HasForeignKey(f => f.ForkFilterListId) 25 | .OnDelete(DeleteBehavior.NoAction); 26 | builder.HasDataJsonFile(); 27 | } 28 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Language.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | 4 | namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; 5 | 6 | public sealed record Language 7 | { 8 | public short Id { get; init; } 9 | public string Iso6391 { get; init; } = null!; 10 | public string Name { get; init; } = null!; 11 | public IEnumerable FilterListLanguages { get; init; } = new List(); 12 | } 13 | 14 | internal sealed class LanguageTypeConfiguration : IEntityTypeConfiguration 15 | { 16 | public void Configure(EntityTypeBuilder builder) 17 | { 18 | builder.Property(l => l.Iso6391) 19 | .IsFixedLength() 20 | .HasMaxLength(2); 21 | builder.HasIndex(l => l.Iso6391) 22 | .IsUnique(); 23 | builder.Property(l => l.Name) 24 | .HasMaxLength(64); 25 | builder.HasIndex(l => l.Name) 26 | .IsUnique(); 27 | builder.HasDataJsonFile(); 28 | } 29 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/License.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | 4 | namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; 5 | 6 | public sealed record License 7 | { 8 | public int Id { get; init; } 9 | public string Name { get; init; } = null!; 10 | public Uri? Url { get; init; } 11 | public bool PermitsModification { get; init; } 12 | public bool PermitsDistribution { get; init; } 13 | public bool PermitsCommercialUse { get; init; } 14 | public IEnumerable FilterLists { get; init; } = new List(); 15 | } 16 | 17 | internal sealed class LicenseTypeConfiguration : IEntityTypeConfiguration 18 | { 19 | public void Configure(EntityTypeBuilder builder) 20 | { 21 | builder.Property(l => l.Name) 22 | .HasMaxLength(64); 23 | builder.HasIndex(l => l.Name) 24 | .IsUnique(); 25 | builder.Property(l => l.Url) 26 | .HasMaxLength(512); 27 | builder.Property(l => l.PermitsModification) 28 | .HasDefaultValue(false); 29 | builder.Property(l => l.PermitsDistribution) 30 | .HasDefaultValue(false); 31 | builder.Property(l => l.PermitsCommercialUse) 32 | .HasDefaultValue(false); 33 | builder.HasDataJsonFile(); 34 | } 35 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Maintainer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | 4 | namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; 5 | 6 | public sealed record Maintainer 7 | { 8 | public int Id { get; init; } 9 | public string Name { get; init; } = null!; 10 | public Uri? Url { get; init; } 11 | public string? EmailAddress { get; init; } 12 | public string? TwitterHandle { get; init; } 13 | public IEnumerable FilterListMaintainers { get; init; } = new List(); 14 | } 15 | 16 | internal sealed class MaintainerTypeConfiguration : IEntityTypeConfiguration 17 | { 18 | public void Configure(EntityTypeBuilder builder) 19 | { 20 | builder.Property(m => m.Name) 21 | .HasMaxLength(64); 22 | builder.HasIndex(m => m.Name) 23 | .IsUnique(); 24 | builder.Property(m => m.Url) 25 | .HasMaxLength(512); 26 | builder.Property(m => m.EmailAddress) 27 | .HasMaxLength(256); 28 | builder.Property(m => m.TwitterHandle) 29 | .HasMaxLength(32); 30 | builder.HasDataJsonFile(); 31 | } 32 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Merge.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | 4 | namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; 5 | 6 | public sealed record Merge 7 | { 8 | public int IncludedInFilterListId { get; init; } 9 | public FilterList IncludedInFilterList { get; init; } = null!; 10 | public int IncludesFilterListId { get; init; } 11 | public FilterList IncludesFilterList { get; init; } = null!; 12 | } 13 | 14 | internal sealed class MergeTypeConfiguration : IEntityTypeConfiguration 15 | { 16 | public void Configure(EntityTypeBuilder builder) 17 | { 18 | builder.HasKey(m => new { m.IncludedInFilterListId, m.IncludesFilterListId }); 19 | builder.HasOne(m => m.IncludedInFilterList) 20 | .WithMany(fl => fl.IncludesFilterLists) 21 | .HasForeignKey(m => m.IncludedInFilterListId); 22 | builder.HasOne(m => m.IncludesFilterList) 23 | .WithMany(fl => fl.IncludedInFilterLists) 24 | .HasForeignKey(m => m.IncludesFilterListId) 25 | .OnDelete(DeleteBehavior.NoAction); 26 | builder.HasDataJsonFile(); 27 | } 28 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Software.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | 4 | namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; 5 | 6 | public sealed record Software 7 | { 8 | public short Id { get; init; } 9 | public string Name { get; init; } = null!; 10 | public string? Description { get; init; } 11 | public Uri? HomeUrl { get; init; } 12 | public Uri? DownloadUrl { get; init; } 13 | public bool SupportsAbpUrlScheme { get; init; } 14 | public IEnumerable SoftwareSyntaxes { get; init; } = new List(); 15 | } 16 | 17 | internal sealed class SoftwareTypeConfiguration : IEntityTypeConfiguration 18 | { 19 | public void Configure(EntityTypeBuilder builder) 20 | { 21 | builder.Property(s => s.Name) 22 | .HasMaxLength(64); 23 | builder.HasIndex(s => s.Name) 24 | .IsUnique(); 25 | builder.Property(s => s.HomeUrl) 26 | .HasMaxLength(512); 27 | builder.Property(s => s.DownloadUrl) 28 | .HasMaxLength(512); 29 | builder.Property(s => s.SupportsAbpUrlScheme) 30 | .HasDefaultValue(false); 31 | builder.HasDataJsonFile(); 32 | } 33 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/SoftwareSyntax.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | 4 | namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; 5 | 6 | public sealed record SoftwareSyntax 7 | { 8 | public short SoftwareId { get; init; } 9 | public Software Software { get; init; } = null!; 10 | public short SyntaxId { get; init; } 11 | public Syntax Syntax { get; init; } = null!; 12 | } 13 | 14 | internal sealed class SoftwareSyntaxTypeConfiguration : IEntityTypeConfiguration 15 | { 16 | public void Configure(EntityTypeBuilder builder) 17 | { 18 | builder.HasKey(ss => new { ss.SoftwareId, ss.SyntaxId }); 19 | builder.HasDataJsonFile(); 20 | } 21 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Syntax.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | 4 | namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; 5 | 6 | public sealed record Syntax 7 | { 8 | public short Id { get; init; } 9 | public string Name { get; init; } = null!; 10 | public string? Description { get; init; } 11 | public Uri? Url { get; init; } 12 | public IEnumerable FilterListSyntaxes { get; init; } = new List(); 13 | public IEnumerable SoftwareSyntaxes { get; init; } = new List(); 14 | } 15 | 16 | internal sealed class SyntaxTypeConfiguration : IEntityTypeConfiguration 17 | { 18 | public void Configure(EntityTypeBuilder builder) 19 | { 20 | builder.Property(s => s.Name) 21 | .HasMaxLength(64); 22 | builder.HasIndex(s => s.Name) 23 | .IsUnique(); 24 | builder.Property(s => s.Url) 25 | .HasMaxLength(512); 26 | builder.HasDataJsonFile(); 27 | } 28 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Tag.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | 4 | namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; 5 | 6 | public sealed record Tag 7 | { 8 | public int Id { get; init; } 9 | public string Name { get; init; } = null!; 10 | public string? Description { get; init; } 11 | public IEnumerable FilterListTags { get; init; } = new List(); 12 | } 13 | 14 | internal sealed class TagTypeConfiguration : IEntityTypeConfiguration 15 | { 16 | public void Configure(EntityTypeBuilder builder) 17 | { 18 | builder.Property(t => t.Name) 19 | .HasMaxLength(32); 20 | builder.HasIndex(t => t.Name) 21 | .IsUnique(); 22 | builder.HasDataJsonFile(); 23 | } 24 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/MigrationService.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Storage; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Hosting; 8 | using OpenTelemetry.Trace; 9 | 10 | namespace FilterLists.Directory.Infrastructure.Persistence.Queries; 11 | 12 | public sealed class MigrationService(IServiceProvider serviceProvider) : IHostedLifecycleService 13 | { 14 | private static readonly ActivitySource ActivitySource = new(nameof(MigrationService)); 15 | 16 | public Task StartAsync(CancellationToken cancellationToken) 17 | { 18 | return Task.CompletedTask; 19 | } 20 | 21 | public Task StopAsync(CancellationToken cancellationToken) 22 | { 23 | return Task.CompletedTask; 24 | } 25 | 26 | public async Task StartingAsync(CancellationToken cancellationToken) 27 | { 28 | using var activity = ActivitySource.StartActivity(); 29 | 30 | try 31 | { 32 | using var scope = serviceProvider.CreateScope(); 33 | await using var dbContext = scope.ServiceProvider.GetRequiredService(); 34 | var dbCreator = dbContext.GetService(); 35 | var strategy = dbContext.Database.CreateExecutionStrategy(); 36 | await strategy.ExecuteAsync(async () => 37 | { 38 | if (!await dbCreator.ExistsAsync(cancellationToken)) await dbCreator.CreateAsync(cancellationToken); 39 | }); 40 | await dbContext.Database.MigrateAsync(cancellationToken); 41 | } 42 | catch (Exception ex) 43 | { 44 | activity?.RecordException(ex); 45 | throw; 46 | } 47 | } 48 | 49 | public Task StartedAsync(CancellationToken cancellationToken) 50 | { 51 | return Task.CompletedTask; 52 | } 53 | 54 | public Task StoppingAsync(CancellationToken cancellationToken) 55 | { 56 | return Task.CompletedTask; 57 | } 58 | 59 | public Task StoppedAsync(CancellationToken cancellationToken) 60 | { 61 | return Task.CompletedTask; 62 | } 63 | } -------------------------------------------------------------------------------- /services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/SeedExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | 4 | namespace FilterLists.Directory.Infrastructure.Persistence.Queries; 5 | 6 | internal static class SeedConfigurationExtensions 7 | { 8 | private static readonly JsonSerializerOptions CamelCaseJsonSerializerOptions = 9 | new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; 10 | 11 | public static void HasDataJsonFile(this EntityTypeBuilder entityTypeBuilder) where TEntity : class 12 | { 13 | var entities = Deserialize(); 14 | if (entities.Count == 0) return; 15 | 16 | entityTypeBuilder.HasData(entities); 17 | } 18 | 19 | public static void HasDataJsonFile(this OwnedNavigationBuilder ownedNavigationBuilder) 20 | where TEntity : class 21 | { 22 | var entities = Deserialize(); 23 | if (entities.Count == 0) return; 24 | 25 | ownedNavigationBuilder.HasData(entities); 26 | } 27 | 28 | private static List Deserialize() 29 | { 30 | // uncomment to short-circuit HasData() when adding a migration 31 | // return new List(); 32 | 33 | var path = Path.Combine("../data", $"{typeof(TEntity).Name}.json"); 34 | if (!File.Exists(path)) return []; 35 | 36 | var entitiesJson = File.ReadAllText(path); 37 | var entities = JsonSerializer.Deserialize>( 38 | entitiesJson, 39 | CamelCaseJsonSerializerOptions); 40 | return entities is null ? [] : entities.ToList(); 41 | } 42 | } -------------------------------------------------------------------------------- /services/Directory/data/Dependent.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "dependencyFilterListId": 60, 4 | "dependentFilterListId": 1816 5 | }, 6 | { 7 | "dependencyFilterListId": 97, 8 | "dependentFilterListId": 819 9 | }, 10 | { 11 | "dependencyFilterListId": 97, 12 | "dependentFilterListId": 852 13 | }, 14 | { 15 | "dependencyFilterListId": 97, 16 | "dependentFilterListId": 853 17 | }, 18 | { 19 | "dependencyFilterListId": 224, 20 | "dependentFilterListId": 2564 21 | }, 22 | { 23 | "dependencyFilterListId": 347, 24 | "dependentFilterListId": 1817 25 | }, 26 | { 27 | "dependencyFilterListId": 2439, 28 | "dependentFilterListId": 2440 29 | }, 30 | { 31 | "dependencyFilterListId": 2439, 32 | "dependentFilterListId": 2441 33 | }, 34 | { 35 | "dependencyFilterListId": 2439, 36 | "dependentFilterListId": 2442 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /services/Directory/data/Fork.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "forkFilterListId": 350, 4 | "upstreamFilterListId": 12 5 | }, 6 | { 7 | "forkFilterListId": 563, 8 | "upstreamFilterListId": 16 9 | }, 10 | { 11 | "forkFilterListId": 16, 12 | "upstreamFilterListId": 59 13 | }, 14 | { 15 | "forkFilterListId": 16, 16 | "upstreamFilterListId": 99 17 | }, 18 | { 19 | "forkFilterListId": 158, 20 | "upstreamFilterListId": 258 21 | }, 22 | { 23 | "forkFilterListId": 163, 24 | "upstreamFilterListId": 262 25 | }, 26 | { 27 | "forkFilterListId": 16, 28 | "upstreamFilterListId": 278 29 | }, 30 | { 31 | "forkFilterListId": 164, 32 | "upstreamFilterListId": 293 33 | }, 34 | { 35 | "forkFilterListId": 167, 36 | "upstreamFilterListId": 295 37 | }, 38 | { 39 | "forkFilterListId": 166, 40 | "upstreamFilterListId": 301 41 | }, 42 | { 43 | "forkFilterListId": 565, 44 | "upstreamFilterListId": 301 45 | }, 46 | { 47 | "forkFilterListId": 312, 48 | "upstreamFilterListId": 311 49 | }, 50 | { 51 | "forkFilterListId": 313, 52 | "upstreamFilterListId": 311 53 | } 54 | ] 55 | -------------------------------------------------------------------------------- /services/Directory/data/Syntax.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Hosts (localhost IPv4)", 5 | "url": "https://en.wikipedia.org/wiki/Hosts_(file)" 6 | }, 7 | { 8 | "id": 2, 9 | "name": "Domains" 10 | }, 11 | { 12 | "id": 3, 13 | "name": "Adblock Plus", 14 | "url": "https://adblockplus.org/filters" 15 | }, 16 | { 17 | "id": 4, 18 | "name": "uBlock Origin Static", 19 | "url": "https://github.com/gorhill/uBlock/wiki/Static-filter-syntax" 20 | }, 21 | { 22 | "id": 6, 23 | "name": "AdGuard", 24 | "url": "https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters" 25 | }, 26 | { 27 | "id": 7, 28 | "name": "uMatrix/uBO lined dynamic rules", 29 | "url": "https://github.com/gorhill/uBlock/wiki/Dynamic-filtering:-quick-guide" 30 | }, 31 | { 32 | "id": 8, 33 | "name": "URLs" 34 | }, 35 | { 36 | "id": 9, 37 | "name": "IPs (IPv4)" 38 | }, 39 | { 40 | "id": 10, 41 | "name": "Tracking Protection List (IE)", 42 | "url": "https://blogs.msdn.microsoft.com/ie/2010/12/07/ie9-and-privacy-introducing-tracking-protection/" 43 | }, 44 | { 45 | "id": 11, 46 | "name": "Redirector" 47 | }, 48 | { 49 | "id": 13, 50 | "name": "MinerBlock", 51 | "url": "https://github.com/xd4rker/MinerBlock" 52 | }, 53 | { 54 | "id": 14, 55 | "name": "Non-localhost hosts (IPv4)", 56 | "url": "https://en.wikipedia.org/wiki/Hosts_(file)" 57 | }, 58 | { 59 | "id": 15, 60 | "name": "IPs (Start-end-range)" 61 | }, 62 | { 63 | "id": 16, 64 | "name": "Domains with wildcards" 65 | }, 66 | { 67 | "id": 17, 68 | "name": "uBlock Origin scriptlet injection", 69 | "url": "https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#scriptlet-injection" 70 | }, 71 | { 72 | "id": 18, 73 | "name": "Little Snitch subscription rules", 74 | "url": "https://help.obdev.at/littlesnitch/lsc-rule-group-subscriptions" 75 | }, 76 | { 77 | "id": 19, 78 | "name": "Privoxy action file", 79 | "url": "https://www.privoxy.org/user-manual/actions-file.html" 80 | }, 81 | { 82 | "id": 20, 83 | "name": "dnsmasq domains list" 84 | }, 85 | { 86 | "id": 21, 87 | "name": "!#include compilation", 88 | "url": "https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#include-file-name" 89 | }, 90 | { 91 | "id": 22, 92 | "name": "DNS servers" 93 | }, 94 | { 95 | "id": 23, 96 | "name": "Unix-format hosts.deny file" 97 | }, 98 | { 99 | "id": 24, 100 | "name": "Unbound" 101 | }, 102 | { 103 | "id": 25, 104 | "name": "Response Policy Zones (RPZ)" 105 | }, 106 | { 107 | "id": 26, 108 | "name": "BIND" 109 | }, 110 | { 111 | "id": 27, 112 | "name": "Windows command line script" 113 | }, 114 | { 115 | "id": 28, 116 | "name": "Adblocker-syntax domains", 117 | "url": "https://help.eyeo.com/en/adblockplus/how-to-write-filters#basic" 118 | }, 119 | { 120 | "id": 29, 121 | "name": "Socks5" 122 | }, 123 | { 124 | "id": 30, 125 | "name": "Pi-hole RegEx", 126 | "url": "https://docs.pi-hole.net/ftldns/regex/tutorial/" 127 | }, 128 | { 129 | "id": 31, 130 | "name": "URLRedirector" 131 | }, 132 | { 133 | "id": 34, 134 | "name": "CIDRs (IPv4)" 135 | }, 136 | { 137 | "id": 36, 138 | "name": "Hosts (localhost IPv6)", 139 | "url": "https://en.wikipedia.org/wiki/Hosts_(file)" 140 | }, 141 | { 142 | "id": 37, 143 | "name": "Non-localhost hosts (IPv6)", 144 | "url": "https://en.wikipedia.org/wiki/Hosts_(file)" 145 | }, 146 | { 147 | "id": 38, 148 | "name": "Adblock Plus Advanced" 149 | }, 150 | { 151 | "id": 39, 152 | "name": "IPs (IPv6)" 153 | }, 154 | { 155 | "id": 41, 156 | "name": "CIDRs (IPv6)" 157 | }, 158 | { 159 | "id": 44, 160 | "name": "PowerShell PKG file" 161 | }, 162 | { 163 | "id": 46, 164 | "name": "''$important''/''$empty'' only" 165 | }, 166 | { 167 | "id": 47, 168 | "name": "Adblocker-syntax domains w/o ABP tag" 169 | }, 170 | { 171 | "id": 48, 172 | "name": "Domains with ABP tag" 173 | }, 174 | { 175 | "id": 49, 176 | "name": "SmartDNS" 177 | }, 178 | { 179 | "id": 50, 180 | "name": "Domains for whitelisting" 181 | }, 182 | { 183 | "id": 51, 184 | "name": "uMatrix ruleset recipe", 185 | "url": "https://github.com/gorhill/uMatrix/wiki/Ruleset-recipes" 186 | }, 187 | { 188 | "id": 52, 189 | "name": "personalDNSfilter whitelisting" 190 | }, 191 | { 192 | "id": 53, 193 | "name": "uBO lined dynamic rules w/ noop", 194 | "url": "https://github.com/gorhill/uBlock/wiki/Dynamic-filtering:-quick-guide" 195 | }, 196 | { 197 | "id": 54, 198 | "name": "Hosts (0)", 199 | "url": "https://en.wikipedia.org/wiki/Hosts_(file)" 200 | }, 201 | { 202 | "id": 55, 203 | "name": "AdGuard Superadvanced only", 204 | "url": "https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters" 205 | }, 206 | { 207 | "id": 56, 208 | "name": "Polish Cookie Consent", 209 | "url": "https://polishannoyancefilters.netlify.app/en/PolishCookieConsent/syntax/" 210 | }, 211 | { 212 | "id": 57, 213 | "name": "CSV", 214 | "url": "https://en.wikipedia.org/wiki/Comma-separated_values" 215 | } 216 | ] 217 | -------------------------------------------------------------------------------- /services/Directory/data/Tag.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "Blocks cryptomining and/or cryptojacking", 4 | "id": 1, 5 | "name": "crypto" 6 | }, 7 | { 8 | "description": "Blocks advertisements", 9 | "id": 2, 10 | "name": "ads" 11 | }, 12 | { 13 | "description": "Blocks trackers and other privacy-invasive resources", 14 | "id": 3, 15 | "name": "privacy" 16 | }, 17 | { 18 | "description": "Blocks social media scripts, trackers, widgets, comment sections, etc.", 19 | "id": 4, 20 | "name": "social" 21 | }, 22 | { 23 | "description": "Blocks adblock detection scripts", 24 | "id": 5, 25 | "name": "anti-adblock" 26 | }, 27 | { 28 | "description": "Blocks malicious resources", 29 | "id": 6, 30 | "name": "malware" 31 | }, 32 | { 33 | "description": "Blocks phishing and/or scam resources", 34 | "id": 7, 35 | "name": "phishing" 36 | }, 37 | { 38 | "description": "Blocks cookie notices primarily in response to the EU Cookie Law and GDPR", 39 | "id": 8, 40 | "name": "cookies" 41 | }, 42 | { 43 | "description": "Blocks subjectively annoying resources", 44 | "id": 9, 45 | "name": "annoyances" 46 | }, 47 | { 48 | "description": "Unblocks categorical resources", 49 | "id": 10, 50 | "name": "allowlist" 51 | }, 52 | { 53 | "description": "Blocks adult, NSFW, pornographic, etc. resources", 54 | "id": 11, 55 | "name": "nsfw" 56 | }, 57 | { 58 | "description": "Redirects traffic through proxies to get around firewalls or service shutdowns", 59 | "id": 12, 60 | "name": "proxy" 61 | }, 62 | { 63 | "description": "Extends or blocks functionality from search engines", 64 | "id": 13, 65 | "name": "search" 66 | }, 67 | { 68 | "description": "Intended for research only", 69 | "id": 14, 70 | "name": "research" 71 | }, 72 | { 73 | "description": "Blocks specific topics/things", 74 | "id": 15, 75 | "name": "topical" 76 | }, 77 | { 78 | "description": "Removes obstructing or annoying overlays", 79 | "id": 16, 80 | "name": "overlay" 81 | }, 82 | { 83 | "description": "Blocks gambling resources", 84 | "id": 17, 85 | "name": "gambling" 86 | }, 87 | { 88 | "description": "Removes website-embedded fonts", 89 | "id": 18, 90 | "name": "fonts" 91 | }, 92 | { 93 | "description": "Blocks resources from certain companies", 94 | "id": 19, 95 | "name": "anti-corp" 96 | }, 97 | { 98 | "description": "Lists that are of special interest to IT admins", 99 | "id": 20, 100 | "name": "admin" 101 | }, 102 | { 103 | "description": "Lists that remove news stories of subjectively low quality", 104 | "id": 21, 105 | "name": "clickbait" 106 | }, 107 | { 108 | "description": "Blocks religious or superstitious content", 109 | "id": 22, 110 | "name": "religious" 111 | }, 112 | { 113 | "description": "Blocks pages from link-shortening services", 114 | "id": 23, 115 | "name": "shorteners" 116 | }, 117 | { 118 | "description": "Blocks piracy-focusing sites", 119 | "id": 24, 120 | "name": "piracy" 121 | }, 122 | { 123 | "description": "Blocks political content", 124 | "id": 25, 125 | "name": "politics" 126 | }, 127 | { 128 | "description": "Blocks software updates", 129 | "id": 26, 130 | "name": "updates" 131 | }, 132 | { 133 | "description": "Userstyles in adblocker syntax that change the appearance of sites", 134 | "id": 27, 135 | "name": "userstyle" 136 | }, 137 | { 138 | "description": "Blocks requests that ask you to subscribe to newsletters", 139 | "id": 28, 140 | "name": "newsletters" 141 | }, 142 | { 143 | "description": "Prevents \"How can I help you?\" prompts from popping up", 144 | "id": 29, 145 | "name": "helpprompt" 146 | }, 147 | { 148 | "description": "Blocks certain cultural content", 149 | "id": 30, 150 | "name": "cultural" 151 | }, 152 | { 153 | "description": "Removes empty boxes that remain after other things were blocked", 154 | "id": 31, 155 | "name": "empty-box" 156 | }, 157 | { 158 | "description": "Blocks prompts to subscribe to push notifications", 159 | "id": 32, 160 | "name": "push-notes" 161 | }, 162 | { 163 | "description": "Blocks promotions of websites' own mobile apps", 164 | "id": 33, 165 | "name": "app-download" 166 | }, 167 | { 168 | "description": "Lists designed for units that are neither PCs or phones, e.g. TVs, game consoles, smart-sticks, etc.", 169 | "id": 34, 170 | "name": "media-unit" 171 | }, 172 | { 173 | "description": "Lists that deal with specific sites or site groups only, without blocking them entirely", 174 | "id": 35, 175 | "name": "site-specific" 176 | }, 177 | { 178 | "description": "Lists known to be so big as to begin to cause loading/processing issues on weaker units, e.g. old phones", 179 | "id": 36, 180 | "name": "≥250K entries" 181 | }, 182 | { 183 | "description": "Blocks Dynamic DNS (DDNS) address providers and hosts", 184 | "id": 37, 185 | "name": "dynamic-domains" 186 | }, 187 | { 188 | "description": "Pre-stable lists where maintainers are extra interested in feedback", 189 | "id": 38, 190 | "name": "beta" 191 | }, 192 | { 193 | "description": "Replaces file and page sources to make them load faster", 194 | "id": 39, 195 | "name": "cdn-replacer" 196 | }, 197 | { 198 | "description": "Bypasses paywalls on websites", 199 | "id": 40, 200 | "name": "paywall" 201 | }, 202 | { 203 | "description": "Cleans up anime viewing websites to make them easier to use", 204 | "id": 41, 205 | "name": "anime" 206 | } 207 | ] 208 | -------------------------------------------------------------------------------- /services/Directory/data/lint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Sorts json files by FilterLists conventions documented in Wiki. 3 | 4 | jq -S ".|=sort_by(.dependencyFilterListId, .dependentFilterListId)" Dependent.json > Dependent.tmp 5 | mv Dependent.tmp Dependent.json 6 | 7 | jq -S ".|=sort_by(.id)" FilterList.json > FilterList.tmp 8 | mv FilterList.tmp FilterList.json 9 | 10 | jq -S ".|=sort_by(.filterListId, .languageId)" FilterListLanguage.json > FilterListLanguage.tmp 11 | mv FilterListLanguage.tmp FilterListLanguage.json 12 | 13 | jq -S ".|=sort_by(.filterListId, .maintainerId)" FilterListMaintainer.json > FilterListMaintainer.tmp 14 | mv FilterListMaintainer.tmp FilterListMaintainer.json 15 | 16 | jq -S ".|=sort_by(.filterListId, .syntaxId)" FilterListSyntax.json > FilterListSyntax.tmp 17 | mv FilterListSyntax.tmp FilterListSyntax.json 18 | 19 | jq -S ".|=sort_by(.filterListId, .tagId)" FilterListTag.json > FilterListTag.tmp 20 | mv FilterListTag.tmp FilterListTag.json 21 | 22 | jq -S ".|=sort_by(.id)" FilterListViewUrl.json > FilterListViewUrl.tmp 23 | mv FilterListViewUrl.tmp FilterListViewUrl.json 24 | 25 | jq -S ".|=sort_by(.upstreamFilterListId, .forkFilterListId)" Fork.json > Fork.tmp 26 | mv Fork.tmp Fork.json 27 | 28 | jq -S ".|=sort_by(.iso6391)" Language.json > Language.tmp 29 | mv Language.tmp Language.json 30 | 31 | jq -S ".|=sort_by(.id)" License.json > License.tmp 32 | mv License.tmp License.json 33 | 34 | jq -S ".|=sort_by(.id)" Maintainer.json > Maintainer.tmp 35 | mv Maintainer.tmp Maintainer.json 36 | 37 | jq -S ".|=sort_by(.includedInFilterListId, .includesFilterListId)" Merge.json > Merge.tmp 38 | mv Merge.tmp Merge.json 39 | 40 | jq -S ".|=sort_by(.id)" Software.json > Software.tmp 41 | mv Software.tmp Software.json 42 | 43 | jq -S ".|=sort_by(.softwareId, .syntaxId)" SoftwareSyntax.json > SoftwareSyntax.tmp 44 | mv SoftwareSyntax.tmp SoftwareSyntax.json 45 | 46 | jq -S ".|=sort_by(.id)" Syntax.json > Syntax.tmp 47 | mv Syntax.tmp Syntax.json 48 | 49 | jq -S ".|=sort_by(.id)" Tag.json > Tag.tmp 50 | mv Tag.tmp Tag.json 51 | -------------------------------------------------------------------------------- /services/FilterLists.AppHost/AppHost.cs: -------------------------------------------------------------------------------- 1 | using Projects; 2 | 3 | var builder = DistributedApplication.CreateBuilder(args); 4 | 5 | var directoryDb = builder.AddSqlServer("directorysqlserver") 6 | .WithDataVolume() 7 | .AddDatabase("directorydb"); 8 | 9 | builder.AddProject("directoryapi") 10 | .WithReference(directoryDb) 11 | .WaitFor(directoryDb) 12 | .WithExternalHttpEndpoints(); 13 | 14 | builder.Build().Run(); -------------------------------------------------------------------------------- /services/FilterLists.AppHost/FilterLists.AppHost.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Exe 7 | net9.0 8 | c7967ef0-ced7-4e18-8282-7302b3c0002b 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /services/FilterLists.AppHost/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "https": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": true, 8 | "applicationUrl": "https://localhost:17145;http://localhost:15172", 9 | "environmentVariables": { 10 | "ASPNETCORE_ENVIRONMENT": "Development", 11 | "DOTNET_ENVIRONMENT": "Development", 12 | "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21129", 13 | "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22052" 14 | } 15 | }, 16 | "http": { 17 | "commandName": "Project", 18 | "dotnetRunMessages": true, 19 | "launchBrowser": true, 20 | "applicationUrl": "http://localhost:15172", 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development", 23 | "DOTNET_ENVIRONMENT": "Development", 24 | "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19265", 25 | "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20065" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /services/FilterLists.AppHost/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /services/FilterLists.AppHost/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning", 6 | "Aspire.Hosting.Dcp": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /services/FilterLists.ServiceDefaults/Extensions.cs: -------------------------------------------------------------------------------- 1 | using Azure.Monitor.OpenTelemetry.AspNetCore; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Diagnostics.HealthChecks; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Diagnostics.HealthChecks; 6 | using Microsoft.Extensions.Logging; 7 | using Microsoft.Extensions.ServiceDiscovery; 8 | using OpenTelemetry; 9 | using OpenTelemetry.Metrics; 10 | using OpenTelemetry.Trace; 11 | 12 | namespace Microsoft.Extensions.Hosting; 13 | 14 | // Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. 15 | // This project should be referenced by each service project in your solution. 16 | // To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults 17 | public static class Extensions 18 | { 19 | private const string HealthEndpointPath = "/health"; 20 | private const string AlivenessEndpointPath = "/alive"; 21 | 22 | public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder 23 | { 24 | builder.ConfigureOpenTelemetry(); 25 | 26 | builder.AddDefaultHealthChecks(); 27 | 28 | builder.Services.AddServiceDiscovery(); 29 | 30 | builder.Services.ConfigureHttpClientDefaults(http => 31 | { 32 | // Turn on resilience by default 33 | http.AddStandardResilienceHandler(); 34 | 35 | // Turn on service discovery by default 36 | http.AddServiceDiscovery(); 37 | }); 38 | 39 | // Uncomment the following to restrict the allowed schemes for service discovery. 40 | builder.Services.Configure(options => 41 | { 42 | options.AllowedSchemes = ["https"]; 43 | }); 44 | 45 | return builder; 46 | } 47 | 48 | public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder 49 | { 50 | builder.Logging.AddOpenTelemetry(logging => 51 | { 52 | logging.IncludeFormattedMessage = true; 53 | logging.IncludeScopes = true; 54 | }); 55 | 56 | builder.Services.AddOpenTelemetry() 57 | .WithMetrics(metrics => 58 | { 59 | metrics.AddAspNetCoreInstrumentation() 60 | .AddHttpClientInstrumentation() 61 | .AddRuntimeInstrumentation(); 62 | }) 63 | .WithTracing(tracing => 64 | { 65 | tracing.AddSource(builder.Environment.ApplicationName) 66 | .AddAspNetCoreInstrumentation(options => 67 | // Exclude health check requests from tracing 68 | options.Filter = context => 69 | !context.Request.Path.StartsWithSegments(HealthEndpointPath) 70 | && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath) 71 | ) 72 | // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) 73 | //.AddGrpcClientInstrumentation() 74 | .AddHttpClientInstrumentation(); 75 | }); 76 | 77 | builder.AddOpenTelemetryExporters(); 78 | 79 | return builder; 80 | } 81 | 82 | private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder 83 | { 84 | var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); 85 | 86 | if (useOtlpExporter) 87 | { 88 | builder.Services.AddOpenTelemetry().UseOtlpExporter(); 89 | } 90 | 91 | // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) 92 | if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) 93 | { 94 | builder.Services.AddOpenTelemetry() 95 | .UseAzureMonitor(); 96 | } 97 | 98 | return builder; 99 | } 100 | 101 | public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder 102 | { 103 | builder.Services.AddHealthChecks() 104 | // Add a default liveness check to ensure app is responsive 105 | .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); 106 | 107 | return builder; 108 | } 109 | 110 | public static WebApplication MapDefaultEndpoints(this WebApplication app) 111 | { 112 | // Adding health checks endpoints to applications in non-development environments has security implications. 113 | // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. 114 | // if (app.Environment.IsDevelopment()) 115 | // { 116 | // All health checks must pass for app to be considered ready to accept traffic after starting 117 | app.MapHealthChecks(HealthEndpointPath); 118 | 119 | // Only health checks tagged with the "live" tag must pass for app to be considered alive 120 | app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions 121 | { 122 | Predicate = r => r.Tags.Contains("live") 123 | }); 124 | // } 125 | 126 | return app; 127 | } 128 | } -------------------------------------------------------------------------------- /services/FilterLists.ServiceDefaults/FilterLists.ServiceDefaults.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /services/FilterLists.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.8.0.0 4 | MinimumVisualStudioVersion = 17.8.0.0 5 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FilterLists.AppHost", "FilterLists.AppHost\FilterLists.AppHost.csproj", "{275E279C-F3FB-4360-A138-536FEDE941F1}" 6 | EndProject 7 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FilterLists.ServiceDefaults", "FilterLists.ServiceDefaults\FilterLists.ServiceDefaults.csproj", "{472BCEAB-C845-4870-AC82-437B10CA9CDE}" 8 | EndProject 9 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FilterLists.Directory.Api", "Directory\FilterLists.Directory.Api\FilterLists.Directory.Api.csproj", "{85A14D07-5C37-458A-B74B-4EE67BBA7102}" 10 | EndProject 11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Directory", "Directory", "{C4F6197D-9546-480E-AE92-1FC264D8E88F}" 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FilterLists.Directory.Infrastructure.Migrations", "Directory\FilterLists.Directory.Infrastructure.Migrations\FilterLists.Directory.Infrastructure.Migrations.csproj", "{37170729-4BC9-412F-9ADF-97592A677E90}" 14 | EndProject 15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FilterLists.Directory.Infrastructure", "Directory\FilterLists.Directory.Infrastructure\FilterLists.Directory.Infrastructure.csproj", "{02B4FF36-8012-42CD-BE3E-92475C091A4C}" 16 | EndProject 17 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FilterLists.Directory.Application", "Directory\FilterLists.Directory.Application\FilterLists.Directory.Application.csproj", "{0D4420C3-283B-4C0A-88B4-D39E2033B80F}" 18 | EndProject 19 | Global 20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 21 | Debug|Any CPU = Debug|Any CPU 22 | Release|Any CPU = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {275E279C-F3FB-4360-A138-536FEDE941F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {275E279C-F3FB-4360-A138-536FEDE941F1}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {275E279C-F3FB-4360-A138-536FEDE941F1}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {275E279C-F3FB-4360-A138-536FEDE941F1}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {472BCEAB-C845-4870-AC82-437B10CA9CDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {472BCEAB-C845-4870-AC82-437B10CA9CDE}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {472BCEAB-C845-4870-AC82-437B10CA9CDE}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {472BCEAB-C845-4870-AC82-437B10CA9CDE}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {85A14D07-5C37-458A-B74B-4EE67BBA7102}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {85A14D07-5C37-458A-B74B-4EE67BBA7102}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {85A14D07-5C37-458A-B74B-4EE67BBA7102}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {85A14D07-5C37-458A-B74B-4EE67BBA7102}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {37170729-4BC9-412F-9ADF-97592A677E90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {37170729-4BC9-412F-9ADF-97592A677E90}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {37170729-4BC9-412F-9ADF-97592A677E90}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {37170729-4BC9-412F-9ADF-97592A677E90}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {02B4FF36-8012-42CD-BE3E-92475C091A4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {02B4FF36-8012-42CD-BE3E-92475C091A4C}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {02B4FF36-8012-42CD-BE3E-92475C091A4C}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {02B4FF36-8012-42CD-BE3E-92475C091A4C}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {0D4420C3-283B-4C0A-88B4-D39E2033B80F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {0D4420C3-283B-4C0A-88B4-D39E2033B80F}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {0D4420C3-283B-4C0A-88B4-D39E2033B80F}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {0D4420C3-283B-4C0A-88B4-D39E2033B80F}.Release|Any CPU.Build.0 = Release|Any CPU 49 | EndGlobalSection 50 | GlobalSection(SolutionProperties) = preSolution 51 | HideSolutionNode = FALSE 52 | EndGlobalSection 53 | GlobalSection(ExtensibilityGlobals) = postSolution 54 | SolutionGuid = {019F26B8-9430-4EAB-87B8-E8F52A739F4C} 55 | EndGlobalSection 56 | GlobalSection(NestedProjects) = preSolution 57 | {85A14D07-5C37-458A-B74B-4EE67BBA7102} = {C4F6197D-9546-480E-AE92-1FC264D8E88F} 58 | {37170729-4BC9-412F-9ADF-97592A677E90} = {C4F6197D-9546-480E-AE92-1FC264D8E88F} 59 | {02B4FF36-8012-42CD-BE3E-92475C091A4C} = {C4F6197D-9546-480E-AE92-1FC264D8E88F} 60 | {0D4420C3-283B-4C0A-88B4-D39E2033B80F} = {C4F6197D-9546-480E-AE92-1FC264D8E88F} 61 | EndGlobalSection 62 | EndGlobal 63 | -------------------------------------------------------------------------------- /services/FilterLists.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True 5 | True 6 | True 7 | True -------------------------------------------------------------------------------- /web/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "auto" 3 | } 4 | -------------------------------------------------------------------------------- /web/config-overrides.js: -------------------------------------------------------------------------------- 1 | const { getThemeVariables } = require("antd/dist/theme"); 2 | const { 3 | override, 4 | fixBabelImports, 5 | addLessLoader, 6 | adjustStyleLoaders, 7 | } = require("customize-cra"); 8 | 9 | module.exports = override( 10 | fixBabelImports("import", { 11 | libraryName: "antd", 12 | libraryDirectory: "es", 13 | style: true, 14 | }), 15 | addLessLoader({ 16 | lessOptions: { 17 | javascriptEnabled: true, 18 | modifyVars: { 19 | ...getThemeVariables({ 20 | dark: true, 21 | compact: true, 22 | }), 23 | }, 24 | }, 25 | }), 26 | adjustStyleLoaders(({ use: [, , postcss] }) => { 27 | const postcssOptions = postcss.options; 28 | postcss.options = { postcssOptions }; 29 | }), 30 | ); 31 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "filterlists.web", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^6.6.3", 7 | "@testing-library/react": "^16.3.0", 8 | "@testing-library/user-event": "^14.6.1", 9 | "@types/jest": "^29.5.14", 10 | "@types/node": "^22.15.27", 11 | "@types/react": "^19.1.6", 12 | "@types/react-dom": "^19.1.5", 13 | "@types/react-router-dom": "^5.3.3", 14 | "antd": "^4.24.16", 15 | "babel-plugin-import": "^1.13.8", 16 | "customize-cra": "^1.0.0", 17 | "eslint-config-prettier": "^10.1.5", 18 | "eslint-plugin-prettier": "^5.4.1", 19 | "http-proxy-middleware": "^3.0.5", 20 | "less": "^4.3.0", 21 | "less-loader": "^12.3.0", 22 | "prettier": "^3.3.2", 23 | "react": "^19.1.0", 24 | "react-app-rewired": "^2.1.11", 25 | "react-dom": "^19.1.0", 26 | "react-router-dom": "^5.3.4", 27 | "react-scripts": "^5.0.0", 28 | "slugify": "^1.6.6", 29 | "typescript": "^4.9.5", 30 | "web-vitals": "^3.5.2" 31 | }, 32 | "scripts": { 33 | "start": "react-app-rewired start", 34 | "build": "react-app-rewired build", 35 | "test": "react-app-rewired test", 36 | "eject": "react-scripts eject", 37 | "prettier:check": "prettier --check 'src/**/*.{js,jsx,ts,tsx}'", 38 | "eslint:check": "eslint 'src/**/*.{js,jsx,ts,tsx}'" 39 | }, 40 | "eslintConfig": { 41 | "extends": [ 42 | "react-app", 43 | "react-app/jest", 44 | "plugin:prettier/recommended" 45 | ] 46 | }, 47 | "browserslist": { 48 | "production": [ 49 | ">0.2%", 50 | "not dead", 51 | "not op_mini all" 52 | ], 53 | "development": [ 54 | "last 1 chrome version", 55 | "last 1 firefox version", 56 | "last 1 safari version" 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/public/favicon.ico -------------------------------------------------------------------------------- /web/public/icon_filterlists.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/public/icon_filterlists.png -------------------------------------------------------------------------------- /web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 30 | 31 | 40 | 41 | FilterLists | Subscriptions for uBlock Origin, Adblock Plus, AdGuard, ... 42 | 43 | 44 | 45 | 46 | 57 |
58 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /web/public/logo_filterlists.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/public/logo_filterlists.png -------------------------------------------------------------------------------- /web/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "FilterLists", 3 | "name": "FilterLists | Subscriptions for uBlock Origin, Adblock Plus, AdGuard, ...", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "icon_filterlists.png", 12 | "type": "image/png", 13 | "sizes": "512x512" 14 | } 15 | ], 16 | "start_url": ".", 17 | "display": "standalone", 18 | "theme_color": "#000000", 19 | "background_color": "#ffffff" 20 | } 21 | -------------------------------------------------------------------------------- /web/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /web/public/tpl-redirect.js: -------------------------------------------------------------------------------- 1 | if (window.navigator.msPointerEnabled) { 2 | window.location = "https://filterlists.com/tpl.html"; 3 | } 4 | -------------------------------------------------------------------------------- /web/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from "@testing-library/react"; 2 | import { App } from "./App"; 3 | 4 | xtest("renders learn react link", () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /web/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ApiOutlined, 3 | DollarOutlined, 4 | GithubOutlined, 5 | IeOutlined, 6 | TwitterOutlined, 7 | } from "@ant-design/icons"; 8 | import { Layout, Tag } from "antd"; 9 | import { 10 | BrowserRouter as Router, 11 | Link, 12 | Route, 13 | RouteComponentProps, 14 | Switch, 15 | } from "react-router-dom"; 16 | import { ListsTable } from "./components"; 17 | 18 | const { Header, Content, Footer } = Layout; 19 | 20 | export const App: React.FC = () => ( 21 | 22 | 23 |
24 | 25 |
26 | 27 |
34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 |
48 | 49 | 50 | 51 | 52 | 53 | 54 |
55 |
56 |
57 | ); 58 | 59 | const Logo = () => ( 60 | 61 | FilterLists logo 66 | 67 | ); 68 | 69 | const NotFound = (props: RouteComponentProps) => ( 70 |

71 | 404 Not Found: {props.location.pathname} 72 |

73 | ); 74 | 75 | const CopyrightAuthor = () => ( 76 | 77 | ©{new Date().getFullYear()}  78 | 84 | Collin M. Barrett 85 | 86 | 87 | ); 88 | 89 | const Twitter = () => ( 90 | 91 | 97 | Twitter 98 | 99 | 100 | ); 101 | 102 | const GitHub = () => ( 103 | 104 | 110 | GitHub 111 | 112 | 113 | ); 114 | 115 | const Api = () => ( 116 | 117 | 123 | API 124 | 125 | 126 | ); 127 | 128 | const Tpl = () => ( 129 | 130 | 134 | TPL (IE) 135 | 136 | 137 | ); 138 | 139 | const Donate = () => ( 140 | 141 | 147 | Donate 148 | 149 | 150 | ); 151 | -------------------------------------------------------------------------------- /web/src/components/Description.tsx: -------------------------------------------------------------------------------- 1 | interface Props { 2 | description?: string; 3 | } 4 | 5 | export const Description = (props: Props) =>

{props.description}

; 6 | -------------------------------------------------------------------------------- /web/src/components/LicenseTag.tsx: -------------------------------------------------------------------------------- 1 | import { Tag } from "antd"; 2 | 3 | interface Props { 4 | name: string; 5 | url?: string; 6 | showLabel?: boolean; 7 | } 8 | 9 | export const LicenseTag = (props: Props) => 10 | props.name ? ( 11 | 12 | {props.showLabel &&

License:

} 13 | 14 | 15 | 16 |
17 | ) : null; 18 | 19 | const TagContents = (props: Props) => 20 | props.url ? ( 21 | 27 | {props.name} 28 | 29 | ) : ( 30 | <>{props.name} 31 | ); 32 | -------------------------------------------------------------------------------- /web/src/components/LinkButton.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "antd"; 2 | import { ButtonType } from "antd/lib/button"; 3 | 4 | interface Props { 5 | url?: string; 6 | text: string; 7 | title?: string; 8 | type?: ButtonType; 9 | icon?: React.ReactNode; 10 | } 11 | 12 | export const LinkButton = (props: Props) => 13 | props.url && props.text ? ( 14 | 25 | ) : null; 26 | -------------------------------------------------------------------------------- /web/src/components/ListInfoButton.tsx: -------------------------------------------------------------------------------- 1 | import { InfoCircleOutlined } from "@ant-design/icons"; 2 | import { Button } from "antd"; 3 | import { RouteComponentProps } from "react-router-dom"; 4 | import slugify from "slugify"; 5 | import { SlugifyOptions } from "../constants"; 6 | 7 | interface Props { 8 | listName: string; 9 | } 10 | 11 | export const ListInfoButton = (props: RouteComponentProps & Props) => { 12 | const listPath = `/lists/${slugify(props.listName, SlugifyOptions)}`; 13 | return ( 14 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /web/src/components/SubscribeButtons.tsx: -------------------------------------------------------------------------------- 1 | import { ImportOutlined } from "@ant-design/icons"; 2 | import { Button } from "antd"; 3 | import { ButtonType } from "antd/lib/button"; 4 | 5 | interface Props { 6 | name: string; 7 | viewUrls: string[]; 8 | } 9 | 10 | export const SubscribeButtons = (props: Props) => 11 | props.viewUrls.length ? ( 12 | <> 13 | 19 | 23 | 24 | ) : null; 25 | 26 | interface MirrorButtonsProps { 27 | name: string; 28 | viewUrlMirrors: string[] | undefined; 29 | } 30 | 31 | const MirrorButtons = (props: MirrorButtonsProps) => ( 32 | <> 33 | {props.viewUrlMirrors && props.viewUrlMirrors.length 34 | ? props.viewUrlMirrors.map((viewUrlMirror: string, i: number) => ( 35 | 42 | )) 43 | : null} 44 | 45 | ); 46 | 47 | interface SubscribeButtonProps { 48 | name: string; 49 | viewUrl: string; 50 | text: string; 51 | isPrimary: boolean; 52 | } 53 | 54 | const SubscribeButton = (props: SubscribeButtonProps) => { 55 | const buttonProps = buildButtonProps( 56 | props.name, 57 | props.viewUrl, 58 | props.isPrimary, 59 | ); 60 | return ( 61 | 71 | ); 72 | }; 73 | 74 | const buildButtonProps = ( 75 | name: string, 76 | viewUrl: string, 77 | isPrimary: boolean, 78 | ) => { 79 | let type: ButtonType = isPrimary ? "primary" : "default"; 80 | let hidden: boolean = false; 81 | 82 | const hrefLocation = `${encodeURIComponent(viewUrl)}`; 83 | const hrefTitle = `${encodeURIComponent(name)}`; 84 | let href = `abp:subscribe?location=${hrefLocation}&title=${hrefTitle}`; 85 | 86 | let prefixes: string[] = []; 87 | let message = `Subscribe to ${name} with a browser extension supporting the "abp:" protocol (e.g. uBlock Origin, Adblock Plus).`; 88 | 89 | // HTTP protocols 90 | if (viewUrl.includes(".onion/")) { 91 | type = "dashed"; 92 | prefixes.push("TOR"); 93 | } 94 | if (viewUrl.includes("http://")) { 95 | type = "dashed"; 96 | prefixes.push("INSECURE"); 97 | } 98 | 99 | // Software protocols 100 | if (viewUrl.includes(".tpl")) { 101 | href = `https://filterlists.com/tpl.html`; 102 | message = `Subscribe via FilterList's TPL page.`; 103 | } 104 | if ( 105 | viewUrl.includes(".lsrules") || 106 | viewUrl.includes("?hostformat=littlesnitch") 107 | ) { 108 | href = `x-littlesnitch:subscribe-rules?url=${hrefLocation}`; 109 | message = `Subscribe to ${name} with Little Snitch's rule group subscription feature.`; 110 | } 111 | 112 | // Formats 113 | if ( 114 | viewUrl.includes(".zip") || 115 | viewUrl.includes(".7z") || 116 | viewUrl.includes(".tar.gz") 117 | ) { 118 | hidden = true; // no known software supports subscribing to compressed lists 119 | } 120 | 121 | const title = `${ 122 | prefixes.length ? prefixes.join(" | ") + " | " : "" 123 | }${message}`; 124 | 125 | return { disabled: hidden, type, href, title }; 126 | }; 127 | -------------------------------------------------------------------------------- /web/src/components/SyntaxTag.tsx: -------------------------------------------------------------------------------- 1 | import { Tag } from "antd"; 2 | 3 | interface Props { 4 | name: string; 5 | definitionUrl?: string; 6 | showLabel?: boolean; 7 | } 8 | 9 | export const SyntaxTag = (props: Props) => 10 | props.name ? ( 11 | 12 | {props.showLabel &&

Syntax:

} 13 | 14 | 15 | 16 |
17 | ) : null; 18 | 19 | const TagContents = (props: Props) => 20 | props.definitionUrl ? ( 21 | 27 | {props.name} 28 | 29 | ) : ( 30 | <>{props.name} 31 | ); 32 | -------------------------------------------------------------------------------- /web/src/components/ViewButtons.tsx: -------------------------------------------------------------------------------- 1 | import { LinkButton } from "./LinkButton"; 2 | import { SearchOutlined } from "@ant-design/icons"; 3 | 4 | interface Props { 5 | name: string; 6 | viewUrls: string[]; 7 | } 8 | 9 | export const ViewButtons = (props: Props) => 10 | props.viewUrls.length ? ( 11 | <> 12 | 13 | 17 | 18 | ) : null; 19 | 20 | interface MirrorButtonsProps { 21 | name: string; 22 | viewUrlMirrors: string[] | undefined; 23 | } 24 | 25 | const MirrorButtons = (props: MirrorButtonsProps) => ( 26 | <> 27 | {props.viewUrlMirrors && props.viewUrlMirrors.length 28 | ? props.viewUrlMirrors.map((viewUrlMirror: string, i: number) => ( 29 | 35 | )) 36 | : null} 37 | 38 | ); 39 | 40 | interface ViewButtonProps { 41 | name: string; 42 | viewUrl: string; 43 | text: string; 44 | } 45 | 46 | const ViewButton = (props: ViewButtonProps) => { 47 | return ( 48 | } 53 | /> 54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /web/src/components/index.ts: -------------------------------------------------------------------------------- 1 | import { ListsTable } from "./listsTable"; 2 | 3 | export { ListsTable }; 4 | -------------------------------------------------------------------------------- /web/src/components/languageCloud/LanguageCloud.tsx: -------------------------------------------------------------------------------- 1 | import { Tag } from "antd"; 2 | import { Language } from "../../interfaces/Language"; 3 | 4 | interface Props { 5 | languages: Language[]; 6 | showLabel?: boolean; 7 | } 8 | 9 | export const LanguageCloud = (props: Props) => 10 | props.languages && props.languages.length ? ( 11 |
12 | {props.showLabel && ( 13 |

{`Language${props.languages.length > 1 ? "s" : ""}:`}

14 | )} 15 | {props.languages.map((l: Language, i: number) => ( 16 | 17 | {l.iso6391} 18 | 19 | ))} 20 |
21 | ) : null; 22 | -------------------------------------------------------------------------------- /web/src/components/languageCloud/index.ts: -------------------------------------------------------------------------------- 1 | import { LanguageCloud } from "./LanguageCloud"; 2 | 3 | export { LanguageCloud }; 4 | -------------------------------------------------------------------------------- /web/src/components/listInfoDrawer/index.ts: -------------------------------------------------------------------------------- 1 | import { ListInfoDrawer } from "./ListInfoDrawer"; 2 | 3 | export { ListInfoDrawer }; 4 | -------------------------------------------------------------------------------- /web/src/components/listInfoDrawer/listInfoDrawer.css: -------------------------------------------------------------------------------- 1 | h3 { 2 | margin-top: 1em; 3 | margin-bottom: 0.2em; 4 | } 5 | 6 | .ant-btn-group 7 | .ant-btn-primary 8 | + .ant-btn:not(.ant-btn-primary):not([disabled]) { 9 | border-left-color: rgb(67, 67, 67); 10 | } 11 | -------------------------------------------------------------------------------- /web/src/components/listsTable/ListDrawer.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Redirect, 3 | Route, 4 | RouteComponentProps, 5 | StaticContext, 6 | } from "react-router"; 7 | import { List } from "../../interfaces/List"; 8 | import { ListInfoDrawer } from "../listInfoDrawer"; 9 | import { Language } from "../../interfaces/Language"; 10 | import { License } from "../../interfaces/License"; 11 | import { Maintainer } from "../../interfaces/Maintainer"; 12 | import { Software } from "../../interfaces/Software"; 13 | import { Syntax } from "../../interfaces/Syntax"; 14 | import { Tag } from "../../interfaces/Tag"; 15 | 16 | interface Props { 17 | lists: List[]; 18 | languages: Language[]; 19 | licenses: License[]; 20 | maintainers: Maintainer[]; 21 | software: Software[]; 22 | syntaxes: Syntax[]; 23 | tags: Tag[]; 24 | } 25 | 26 | export const ListDrawer = (props: Props) => { 27 | const renderDrawer = (rp: RouteComponentProps) => { 28 | const list = props.lists.find((l) => l.slug === rp.match.params.listSlug); 29 | return list ? ( 30 | 40 | ) : props.lists && props.lists.length ? ( 41 | 42 | ) : null; 43 | }; 44 | return ; 45 | }; 46 | -------------------------------------------------------------------------------- /web/src/components/listsTable/ListsTableHoc.tsx: -------------------------------------------------------------------------------- 1 | import { RouteComponentProps } from "react-router-dom"; 2 | import { 3 | useLanguages, 4 | useLicenses, 5 | useLists, 6 | useMaintainers, 7 | useSoftware, 8 | useSyntaxes, 9 | useTags, 10 | } from "../../hooks"; 11 | import { ListDrawer } from "./ListDrawer"; 12 | import { ListsTable } from "./ListsTable"; 13 | 14 | export const ListsTableHoc = (props: RouteComponentProps) => { 15 | const lists = useLists(); 16 | const languages = useLanguages(); 17 | const licenses = useLicenses(); 18 | const maintainers = useMaintainers(); 19 | const software = useSoftware(); 20 | const syntaxes = useSyntaxes(); 21 | const tags = useTags(); 22 | return ( 23 | <> 24 | 34 | 43 | 44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /web/src/components/listsTable/arraySorter.ts: -------------------------------------------------------------------------------- 1 | interface ArraySortableEntity { 2 | id: number; 3 | name: string; 4 | } 5 | 6 | export const arraySorter = ( 7 | a: number[], 8 | b: number[], 9 | entities: ArraySortableEntity[], 10 | ) => 11 | a && a.length 12 | ? b && b.length 13 | ? a.length === b.length 14 | ? entities 15 | .filter((e: ArraySortableEntity) => a.includes(e.id)) 16 | .map((e: ArraySortableEntity) => e.name) 17 | .join() 18 | .toLowerCase() > 19 | entities 20 | .filter((e: ArraySortableEntity) => b.includes(e.id)) 21 | .map((e: ArraySortableEntity) => e.name) 22 | .join() 23 | .toLowerCase() 24 | ? 1 25 | : -1 26 | : a.length > b.length 27 | ? -1 28 | : 1 29 | : -1 30 | : 1; 31 | -------------------------------------------------------------------------------- /web/src/components/listsTable/index.ts: -------------------------------------------------------------------------------- 1 | import { ListsTableHoc } from "./ListsTableHoc"; 2 | 3 | export { ListsTableHoc as ListsTable }; 4 | -------------------------------------------------------------------------------- /web/src/components/maintainerCloud/MaintainerCloud.tsx: -------------------------------------------------------------------------------- 1 | import { Tag } from "antd"; 2 | import { Maintainer } from "../../interfaces/Maintainer"; 3 | 4 | interface Props { 5 | maintainers: Maintainer[]; 6 | } 7 | 8 | export const MaintainerCloud = (props: Props) => 9 | props.maintainers && props.maintainers.length ? ( 10 |
11 | {props.maintainers.map((m: Maintainer, i: number) => 12 | m.url ? ( 13 | 14 | 20 | {m.name} 21 | 22 | 23 | ) : ( 24 | {m.name} 25 | ), 26 | )} 27 |
28 | ) : null; 29 | -------------------------------------------------------------------------------- /web/src/components/maintainerCloud/index.ts: -------------------------------------------------------------------------------- 1 | import { MaintainerCloud } from "./MaintainerCloud"; 2 | 3 | export { MaintainerCloud }; 4 | -------------------------------------------------------------------------------- /web/src/components/maintainers/Maintainers.tsx: -------------------------------------------------------------------------------- 1 | import "./maintainers.css"; 2 | import { HomeOutlined, MailOutlined, TwitterOutlined } from "@ant-design/icons"; 3 | import { Card } from "antd"; 4 | import { Maintainer } from "../../interfaces/Maintainer"; 5 | 6 | interface Props { 7 | maintainers: Maintainer[]; 8 | } 9 | 10 | export const Maintainers = (props: Props) => 11 | props.maintainers && props.maintainers.length ? ( 12 | <> 13 |

Maintainer{props.maintainers.length > 1 ? "s" : ""}:

14 | {props.maintainers.map((m: Maintainer, index: number) => ( 15 | 16 | ))} 17 | 18 | ) : null; 19 | 20 | interface MaintainerComponentProps { 21 | maintainer: Maintainer; 22 | } 23 | 24 | const MaintainerComponent = (props: MaintainerComponentProps) => { 25 | let actions = []; 26 | if (props.maintainer.url) { 27 | actions.push( 28 | 34 | 35 | , 36 | ); 37 | } 38 | if (props.maintainer.emailAddress) { 39 | actions.push( 40 | 46 | 47 | , 48 | ); 49 | } 50 | if (props.maintainer.twitterHandle) { 51 | actions.push( 52 | 58 | 59 | , 60 | ); 61 | } 62 | return ; 63 | }; 64 | -------------------------------------------------------------------------------- /web/src/components/maintainers/index.ts: -------------------------------------------------------------------------------- 1 | import { Maintainers } from "./Maintainers"; 2 | 3 | export { Maintainers }; 4 | -------------------------------------------------------------------------------- /web/src/components/maintainers/maintainers.css: -------------------------------------------------------------------------------- 1 | .ant-card-body { 2 | display: none; 3 | } 4 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/SoftwareCloud.tsx: -------------------------------------------------------------------------------- 1 | import { Software } from "../../interfaces/Software"; 2 | import { SoftwareIcon } from "./SoftwareIcon"; 3 | 4 | interface Props { 5 | software: Software[]; 6 | showLabel?: boolean; 7 | } 8 | 9 | export const SoftwareCloud = (props: Props) => 10 | props.software && props.software.length ? ( 11 |
12 | {props.showLabel &&

{`Software:`}

} 13 | {props.software.map((s: Software, i: number) => 14 | s.homeUrl ? ( 15 | 25 | 26 | 27 | ) : ( 28 | 29 | ), 30 | )} 31 |
32 | ) : null; 33 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/SoftwareIcon.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | img01, 3 | img02, 4 | img03, 5 | img04, 6 | img05, 7 | img06, 8 | img07, 9 | img08, 10 | img10, 11 | img11, 12 | img12, 13 | img13, 14 | img14, 15 | img15, 16 | img16, 17 | img17, 18 | img18, 19 | img19, 20 | img20, 21 | img21, 22 | img22, 23 | img23, 24 | img24, 25 | img25, 26 | img26, 27 | img27, 28 | img28, 29 | img29, 30 | img30, 31 | img31, 32 | img32, 33 | img33, 34 | img34, 35 | img35, 36 | img36, 37 | img37, 38 | img38, 39 | img39, 40 | img40, 41 | img41, 42 | img42, 43 | img43, 44 | img44, 45 | img45, 46 | img46, 47 | img47, 48 | } from "./imgs"; 49 | 50 | interface Props { 51 | id: number; 52 | } 53 | 54 | export const SoftwareIcon = (props: Props) => 55 | icons[props.id] ? ( 56 | {icons[props.id].imageTitle} 62 | ) : null; 63 | 64 | interface Icon { 65 | image: any; 66 | imageTitle: string; 67 | } 68 | 69 | const icons: { [id: number]: Icon } = { 70 | 1: { image: img01, imageTitle: "uBlock Origin" }, 71 | 2: { image: img02, imageTitle: "Adblock Plus" }, 72 | 3: { image: img03, imageTitle: "AdGuard (free versions)" }, 73 | 4: { image: img04, imageTitle: "DNS66" }, 74 | 5: { image: img05, imageTitle: "Nano Adblocker" }, 75 | 6: { image: img06, imageTitle: "AdBlock" }, 76 | 7: { image: img07, imageTitle: "AdAway" }, 77 | 8: { image: img08, imageTitle: "Personal Blocklist" }, 78 | 10: { image: img10, imageTitle: "Redirector" }, 79 | 11: { image: img11, imageTitle: "Hosts File Editor" }, 80 | 12: { image: img12, imageTitle: "Gas Mask" }, 81 | 13: { image: img13, imageTitle: "MinerBlock" }, 82 | 14: { image: img14, imageTitle: "Pi-hole" }, 83 | 15: { image: img15, imageTitle: "uBlock" }, 84 | 16: { image: img16, imageTitle: "Internet Explorer (TPL)" }, 85 | 17: { image: img17, imageTitle: "Google Hit Hider by Domain" }, 86 | 18: { image: img18, imageTitle: "FireHOL" }, 87 | 19: { image: img19, imageTitle: "Samsung Knox" }, 88 | 20: { image: img20, imageTitle: "Little Snitch" }, 89 | 21: { image: img21, imageTitle: "Privoxy" }, 90 | 22: { image: img22, imageTitle: "Diversion" }, 91 | 23: { image: img23, imageTitle: "dnsmasq" }, 92 | 24: { image: img24, imageTitle: "Slimjet" }, 93 | 25: { image: img25, imageTitle: "uMatrix" }, 94 | 26: { image: img26, imageTitle: "Blokada" }, 95 | 27: { image: img27, imageTitle: "hostsmgr" }, 96 | 28: { image: img28, imageTitle: "personalDNSfilter" }, 97 | 29: { image: img29, imageTitle: "Unbound" }, 98 | 30: { image: img30, imageTitle: "BIND" }, 99 | 31: { image: img31, imageTitle: "AdGuard Home" }, 100 | 32: { image: img32, imageTitle: "AdNauseam" }, 101 | 33: { image: img33, imageTitle: "Legacy Unix derivatives" }, 102 | 34: { image: img34, imageTitle: "Windows command line" }, 103 | 35: { image: img35, imageTitle: "Shadowsocks" }, 104 | 36: { image: img36, imageTitle: "ShadowsocksR" }, 105 | 37: { image: img37, imageTitle: "Shadowrocket" }, 106 | 38: { image: img38, imageTitle: "DNSRedirector" }, 107 | 39: { image: img39, imageTitle: "pfBlockerNG" }, 108 | 40: { image: img40, imageTitle: "Opera's built-in adblocker" }, 109 | 41: { image: img41, imageTitle: "Surge" }, 110 | 42: { image: img42, imageTitle: "dnscrypt-proxy" }, 111 | 43: { image: img43, imageTitle: "SmartDNS" }, 112 | 44: { image: img44, imageTitle: "AdGuard for Windows/macOS" }, 113 | 45: { image: img45, imageTitle: "AdGuard for Android" }, 114 | 46: { image: img46, imageTitle: "Vivaldi's Privacy settings" }, 115 | 47: { image: img47, imageTitle: "Polish Cookie Consent" }, 116 | }; 117 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/01-uBlock-Origin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/02-Adblock-Plus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/03-AdGuard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/04-DNS66.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/04-DNS66.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/05-Nano-Adblocker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/05-Nano-Adblocker.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/06-AdBlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/06-AdBlock.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/07-AdAway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/07-AdAway.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/08-Personal-Blocklist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/08-Personal-Blocklist.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/10-Redirector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/10-Redirector.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/11-Hosts-File-Editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/11-Hosts-File-Editor.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/12-Gas-Mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/12-Gas-Mask.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/13-MinerBlock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/14-Pi-hole.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/15-uBlock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/16-Internet-Explorer-TPL.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/17-Google-Hit-Hider-by-Domain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/17-Google-Hit-Hider-by-Domain.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/17-Google-Hit-Hider-by-Domain.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/18-FireHOL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/18-FireHOL.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/19-Samsung-Knox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/19-Samsung-Knox.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/19-Samsung-Knox.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/20-Little-Snitch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/20-Little-Snitch.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/21-Privoxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/21-Privoxy.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/22-Diversion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/22-Diversion.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/22-Diversion.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/23-dnsmasq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/23-dnsmasq.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/24-Slimjet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/24-Slimjet.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/25-uMatrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/25-uMatrix.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/26-Blokada.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/26-Blokada.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/27-hostsmgr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/27-hostsmgr.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/28-personalDNSfilter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/29-Unbound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/29-Unbound.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/30-BIND.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/30-BIND.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/31-AdGuard-Home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/31-AdGuard-Home.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/31-AdGuard-Home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/32-AdNauseam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/32-AdNauseam.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/33-Legacy-Unix-Derivatives.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/33-Legacy-Unix-Derivatives.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/34-Windows-command-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/34-Windows-command-line.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/35-Shadowsocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/35-Shadowsocks.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/36-ShadowsocksR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/36-ShadowsocksR.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/37-Shadowrocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/37-Shadowrocket.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/38-DNSRedirector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/38-DNSRedirector.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/39-pfBlockerNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/39-pfBlockerNG.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/40-Opera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/40-Opera.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/41-Surge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/41-Surge.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/42-dnscrypt-proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/42-dnscrypt-proxy.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/43-SmartDNS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/43-SmartDNS.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/44-AdGuard-for-WindowsMac.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/45-AdGuard-for-Android.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/46-Vivaldi.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/47-PolishCookieConsent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinbarrett/FilterLists/2d08fb07ce715780fafc27d75e8714b74a02d2e8/web/src/components/softwareCloud/imgs/47-PolishCookieConsent.png -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/imgs.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.svg" { 2 | const content: any; 3 | export default content; 4 | } 5 | 6 | declare module "*.png" { 7 | const content: any; 8 | export default content; 9 | } 10 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/imgs/index.ts: -------------------------------------------------------------------------------- 1 | import img01 from "./01-uBlock-Origin.svg"; 2 | import img02 from "./02-Adblock-Plus.svg"; 3 | import img03 from "./03-AdGuard.svg"; 4 | import img04 from "./04-DNS66.png"; 5 | import img05 from "./05-Nano-Adblocker.png"; 6 | import img06 from "./06-AdBlock.png"; 7 | import img07 from "./07-AdAway.png"; 8 | import img08 from "./08-Personal-Blocklist.png"; 9 | import img10 from "./10-Redirector.png"; 10 | import img11 from "./11-Hosts-File-Editor.png"; 11 | import img12 from "./12-Gas-Mask.png"; 12 | import img13 from "./13-MinerBlock.svg"; 13 | import img14 from "./14-Pi-hole.svg"; 14 | import img15 from "./15-uBlock.svg"; 15 | import img16 from "./16-Internet-Explorer-TPL.svg"; 16 | import img17 from "./17-Google-Hit-Hider-by-Domain.svg"; 17 | import img18 from "./18-FireHOL.png"; 18 | import img19 from "./19-Samsung-Knox.svg"; 19 | import img20 from "./20-Little-Snitch.png"; 20 | import img21 from "./21-Privoxy.png"; 21 | import img22 from "./22-Diversion.svg"; 22 | import img23 from "./23-dnsmasq.png"; 23 | import img24 from "./24-Slimjet.png"; 24 | import img25 from "./25-uMatrix.png"; 25 | import img26 from "./26-Blokada.png"; 26 | import img27 from "./27-hostsmgr.png"; 27 | import img28 from "./28-personalDNSfilter.svg"; 28 | import img29 from "./29-Unbound.png"; 29 | import img30 from "./30-BIND.png"; 30 | import img31 from "./31-AdGuard-Home.svg"; 31 | import img32 from "./32-AdNauseam.png"; 32 | import img33 from "./33-Legacy-Unix-Derivatives.png"; 33 | import img34 from "./34-Windows-command-line.png"; 34 | import img35 from "./35-Shadowsocks.png"; 35 | import img36 from "./36-ShadowsocksR.png"; 36 | import img37 from "./37-Shadowrocket.png"; 37 | import img38 from "./38-DNSRedirector.png"; 38 | import img39 from "./39-pfBlockerNG.png"; 39 | import img40 from "./40-Opera.png"; 40 | import img41 from "./41-Surge.png"; 41 | import img42 from "./42-dnscrypt-proxy.png"; 42 | import img43 from "./43-SmartDNS.png"; 43 | import img44 from "./44-AdGuard-for-WindowsMac.svg"; 44 | import img45 from "./45-AdGuard-for-Android.svg"; 45 | import img46 from "./46-Vivaldi.svg"; 46 | import img47 from "./47-PolishCookieConsent.png"; 47 | 48 | export { 49 | img01, 50 | img02, 51 | img03, 52 | img04, 53 | img05, 54 | img06, 55 | img07, 56 | img08, 57 | img10, 58 | img11, 59 | img12, 60 | img13, 61 | img14, 62 | img15, 63 | img16, 64 | img17, 65 | img18, 66 | img19, 67 | img20, 68 | img21, 69 | img22, 70 | img23, 71 | img24, 72 | img25, 73 | img26, 74 | img27, 75 | img28, 76 | img29, 77 | img30, 78 | img31, 79 | img32, 80 | img33, 81 | img34, 82 | img35, 83 | img36, 84 | img37, 85 | img38, 86 | img39, 87 | img40, 88 | img41, 89 | img42, 90 | img43, 91 | img44, 92 | img45, 93 | img46, 94 | img47, 95 | }; 96 | -------------------------------------------------------------------------------- /web/src/components/softwareCloud/index.ts: -------------------------------------------------------------------------------- 1 | import { SoftwareCloud } from "./SoftwareCloud"; 2 | import { SoftwareIcon } from "./SoftwareIcon"; 3 | 4 | export { SoftwareCloud, SoftwareIcon }; 5 | -------------------------------------------------------------------------------- /web/src/components/syntaxCloud/SyntaxCloud.tsx: -------------------------------------------------------------------------------- 1 | import { Syntax } from "../../interfaces/Syntax"; 2 | import { SyntaxTag } from "../SyntaxTag"; 3 | 4 | interface Props { 5 | syntaxes: Syntax[]; 6 | showLabel?: boolean; 7 | } 8 | 9 | export const SyntaxCloud = (props: Props) => 10 | props.syntaxes && props.syntaxes.length ? ( 11 |
12 | {props.showLabel && ( 13 |

{`Syntax${props.syntaxes.length > 1 ? "es" : ""}:`}

14 | )} 15 | {props.syntaxes.map((s: Syntax, i: number) => ( 16 | 17 | ))} 18 |
19 | ) : null; 20 | -------------------------------------------------------------------------------- /web/src/components/syntaxCloud/index.ts: -------------------------------------------------------------------------------- 1 | import { SyntaxCloud } from "./SyntaxCloud"; 2 | 3 | export { SyntaxCloud }; 4 | -------------------------------------------------------------------------------- /web/src/components/tagCloud/TagCloud.tsx: -------------------------------------------------------------------------------- 1 | import { Tag } from "antd"; 2 | import { Tag as TagInterface } from "../../interfaces/Tag"; 3 | 4 | interface Props { 5 | tags: TagInterface[]; 6 | showLabel?: boolean; 7 | } 8 | 9 | export const TagCloud = (props: Props) => 10 | props.tags && props.tags.length ? ( 11 |
12 | {props.showLabel &&

{`Tag${props.tags.length > 1 ? "s" : ""}:`}

} 13 | {props.tags.map((t: TagInterface, i: number) => ( 14 | 15 | {t.name} 16 | 17 | ))} 18 |
19 | ) : null; 20 | -------------------------------------------------------------------------------- /web/src/components/tagCloud/index.ts: -------------------------------------------------------------------------------- 1 | import { TagCloud } from "./TagCloud"; 2 | 3 | export { TagCloud }; 4 | -------------------------------------------------------------------------------- /web/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const SlugifyOptions = { remove: /[^a-zA-Z0-9- ]/g, lower: true }; 2 | -------------------------------------------------------------------------------- /web/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | import { useLanguages } from "./useLanguages"; 2 | import { useLicenses } from "./useLicenses"; 3 | import { useLists } from "./useLists"; 4 | import { useListDetails } from "./useListDetails"; 5 | import { useMaintainers } from "./useMaintainers"; 6 | import { useSearchColumnFilter } from "./useSearchColumnFilter"; 7 | import { useSoftware } from "./useSoftware"; 8 | import { useSyntaxes } from "./useSyntaxes"; 9 | import { useTablePageSizer } from "./useTablePageSizer"; 10 | import { useTags } from "./useTags"; 11 | 12 | export { 13 | useLanguages, 14 | useLicenses, 15 | useLists, 16 | useListDetails, 17 | useMaintainers, 18 | useSearchColumnFilter, 19 | useSoftware, 20 | useSyntaxes, 21 | useTablePageSizer, 22 | useTags, 23 | }; 24 | -------------------------------------------------------------------------------- /web/src/hooks/useApiData.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export const useApiData = (url: string) => { 4 | const [data, setData] = useState(); 5 | useEffect(() => { 6 | const fetchData = async () => 7 | (await fetch(url)).json().then((r) => setData(r)); 8 | fetchData(); 9 | }, [url]); 10 | return data; 11 | }; 12 | -------------------------------------------------------------------------------- /web/src/hooks/useLanguages.tsx: -------------------------------------------------------------------------------- 1 | import { Language } from "../interfaces/Language"; 2 | import { useApiData } from "./useApiData"; 3 | 4 | export const useLanguages = () => 5 | (useApiData("https://api.filterlists.com/languages") || []).sort( 6 | (a: Language, b: Language) => a.name.localeCompare(b.name), 7 | ); 8 | -------------------------------------------------------------------------------- /web/src/hooks/useLicenses.tsx: -------------------------------------------------------------------------------- 1 | import { License } from "../interfaces/License"; 2 | import { useApiData } from "./useApiData"; 3 | 4 | export const useLicenses = () => 5 | (useApiData("https://api.filterlists.com/licenses") || []).sort( 6 | (a: License, b: License) => a.name.localeCompare(b.name), 7 | ); 8 | -------------------------------------------------------------------------------- /web/src/hooks/useListDetails.tsx: -------------------------------------------------------------------------------- 1 | import { ListDetails } from "../interfaces/ListDetails"; 2 | import { useApiData } from "./useApiData"; 3 | 4 | export const useListDetails = (id: number) => { 5 | return useApiData("https://api.filterlists.com/lists/" + id); 6 | }; 7 | -------------------------------------------------------------------------------- /web/src/hooks/useLists.tsx: -------------------------------------------------------------------------------- 1 | import slugify from "slugify"; 2 | import { SlugifyOptions } from "../constants"; 3 | import { List } from "../interfaces/List"; 4 | import { useApiData } from "./useApiData"; 5 | 6 | export const useLists = () => { 7 | const lists = useApiData("https://api.filterlists.com/lists"); 8 | lists && lists.forEach((l) => (l.slug = slugify(l.name, SlugifyOptions))); 9 | return (lists || []).sort((a: List, b: List) => a.name.localeCompare(b.name)); 10 | }; 11 | -------------------------------------------------------------------------------- /web/src/hooks/useMaintainers.tsx: -------------------------------------------------------------------------------- 1 | import { Maintainer } from "../interfaces/Maintainer"; 2 | import { useApiData } from "./useApiData"; 3 | 4 | export const useMaintainers = () => 5 | ( 6 | useApiData("https://api.filterlists.com/maintainers") || [] 7 | ).sort((a: Maintainer, b: Maintainer) => a.name.localeCompare(b.name)); 8 | -------------------------------------------------------------------------------- /web/src/hooks/useSearchColumnFilter.tsx: -------------------------------------------------------------------------------- 1 | import { SearchOutlined } from "@ant-design/icons"; 2 | import { Button, Input } from "antd"; 3 | import React, { useEffect, useState } from "react"; 4 | import { 5 | FilterConfirmProps, 6 | FilterDropdownProps, 7 | } from "antd/lib/table/interface"; 8 | 9 | interface FilterPropsState { 10 | filterDropdown?: 11 | | React.ReactNode 12 | | ((props: FilterDropdownProps) => React.ReactNode); 13 | filterIcon?: React.ReactNode | ((filtered: boolean) => React.ReactNode); 14 | onFilter?: (value: any, record: T) => boolean; 15 | } 16 | 17 | export const useSearchColumnFilter = (dataIndex: string) => { 18 | const [filterProps, setFilterProps] = useState>({ 19 | filterDropdown: undefined, 20 | filterIcon: undefined, 21 | onFilter: undefined, 22 | }); 23 | useEffect(() => { 24 | const handleSearch = (confirm?: (param: FilterConfirmProps) => void) => { 25 | confirm && confirm({ closeDropdown: true }); 26 | }; 27 | const handleReset = (clearFilters?: (selectedKeys: string[]) => void) => { 28 | clearFilters && clearFilters([]); 29 | }; 30 | setFilterProps({ 31 | filterDropdown: ({ 32 | setSelectedKeys, 33 | selectedKeys, 34 | confirm, 35 | clearFilters, 36 | }) => ( 37 |
38 | 44 | setSelectedKeys && 45 | setSelectedKeys(e.target.value ? [e.target.value] : []) 46 | } 47 | onPressEnter={() => handleSearch(confirm)} 48 | style={{ width: 188, marginBottom: 8, display: "block" }} 49 | /> 50 | 59 | 66 |
67 | ), 68 | filterIcon: (filtered) => ( 69 | 70 | ), 71 | onFilter: (value, record) => { 72 | const searchValue = (record as any)[dataIndex]; 73 | return ( 74 | searchValue && 75 | searchValue 76 | .toString() 77 | .toLowerCase() 78 | .includes(value.toString().toLowerCase()) 79 | ); 80 | }, 81 | }); 82 | }, [dataIndex]); 83 | return filterProps; 84 | }; 85 | -------------------------------------------------------------------------------- /web/src/hooks/useSoftware.tsx: -------------------------------------------------------------------------------- 1 | import { Software } from "../interfaces/Software"; 2 | import { useApiData } from "./useApiData"; 3 | 4 | export const useSoftware = () => 5 | (useApiData("https://api.filterlists.com/software") || []).sort( 6 | (a: Software, b: Software) => a.name.localeCompare(b.name), 7 | ); 8 | -------------------------------------------------------------------------------- /web/src/hooks/useSyntaxes.tsx: -------------------------------------------------------------------------------- 1 | import { Syntax } from "../interfaces/Syntax"; 2 | import { useApiData } from "./useApiData"; 3 | 4 | export const useSyntaxes = () => 5 | (useApiData("https://api.filterlists.com/syntaxes") || []).sort( 6 | (a: Syntax, b: Syntax) => a.name.localeCompare(b.name), 7 | ); 8 | -------------------------------------------------------------------------------- /web/src/hooks/useTablePageSizer.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | interface TablePageSize { 4 | pageSize: number; 5 | isNarrowWindow: boolean; 6 | isWideWindow: boolean; 7 | } 8 | 9 | export const useTablePageSizer = () => { 10 | const [state, setState] = useState(calculateSize()); 11 | useEffect(() => { 12 | const updatePageSize = () => setState(calculateSize()); 13 | window.addEventListener("resize", updatePageSize); 14 | return () => window.removeEventListener("resize", updatePageSize); 15 | }, []); 16 | return state; 17 | }; 18 | 19 | const calculateSize = () => ({ 20 | pageSize: 21 | window.innerWidth < 576 22 | ? Math.floor((window.innerHeight - 214) / 51) 23 | : Math.floor((window.innerHeight - 187) / 63), 24 | isNarrowWindow: window.innerWidth < 576 ? true : false, 25 | isWideWindow: window.innerWidth > 1918 ? true : false, 26 | }); 27 | -------------------------------------------------------------------------------- /web/src/hooks/useTags.tsx: -------------------------------------------------------------------------------- 1 | import { Tag } from "../interfaces/Tag"; 2 | import { useApiData } from "./useApiData"; 3 | 4 | export const useTags = () => 5 | (useApiData("https://api.filterlists.com/tags") || []).sort( 6 | (a: Tag, b: Tag) => a.name.localeCompare(b.name), 7 | ); 8 | -------------------------------------------------------------------------------- /web/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | 15 | td.ant-table-cell > * { 16 | height: 42px; 17 | overflow: hidden; 18 | } 19 | 20 | td.ant-table-cell { 21 | padding: 2px 4px 4px 2px; 22 | } 23 | 24 | td.ant-table-column-sort { 25 | background: rgb(20, 20, 20); 26 | } 27 | 28 | .ant-tag { 29 | margin: 0px 8px 2px 0px; 30 | } 31 | -------------------------------------------------------------------------------- /web/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import { App } from "./App"; 4 | import "./index.css"; 5 | import reportWebVitals from "./reportWebVitals"; 6 | 7 | const container = document.getElementById("root"); 8 | const root = createRoot(container!); 9 | 10 | root.render( 11 | 12 | 13 | , 14 | ); 15 | 16 | // If you want to start measuring performance in your app, pass a function 17 | // to log results (for example: reportWebVitals(console.log)) 18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 19 | reportWebVitals(); 20 | -------------------------------------------------------------------------------- /web/src/interfaces/Language.ts: -------------------------------------------------------------------------------- 1 | export interface Language { 2 | id: number; 3 | iso6391: string; 4 | name: string; 5 | filterListIds: number[]; 6 | } 7 | -------------------------------------------------------------------------------- /web/src/interfaces/License.ts: -------------------------------------------------------------------------------- 1 | export interface License { 2 | id: number; 3 | name: string; 4 | url: string; 5 | permitsModification: boolean; 6 | permitsDistribution: boolean; 7 | permitsCommercialUse: boolean; 8 | filterListIds: number[]; 9 | } 10 | -------------------------------------------------------------------------------- /web/src/interfaces/List.ts: -------------------------------------------------------------------------------- 1 | export interface List { 2 | id: number; 3 | name: string; 4 | description?: string; 5 | licenseId?: number; 6 | syntaxIds: number[]; 7 | languageIds: number[]; 8 | tagIds: number[]; 9 | maintainerIds: number[]; 10 | 11 | // auto-generated by useLists hook 12 | slug: string; 13 | } 14 | -------------------------------------------------------------------------------- /web/src/interfaces/ListDetails.ts: -------------------------------------------------------------------------------- 1 | export interface ListDetails { 2 | id: number; 3 | name: string; 4 | description?: string; 5 | licenseId?: number; 6 | syntaxIds: number[]; 7 | languageIds: number[]; 8 | tagIds: number[]; 9 | viewUrls: ViewUrl[]; 10 | homeUrl?: string; 11 | onionUrl?: string; 12 | policyUrl?: string; 13 | submissionUrl?: string; 14 | issuesUrl?: string; 15 | forumUrl?: string; 16 | chatUrl?: string; 17 | emailAddress?: string; 18 | donateUrl?: string; 19 | maintainerIds: number[]; 20 | upstreamFilterListIds: number[]; 21 | forkFilterListIds: number[]; 22 | includedInFilterListIds: number[]; 23 | includesFilterListIds: number[]; 24 | dependencyFilterListIds: number[]; 25 | dependentFilterListIds: number[]; 26 | } 27 | 28 | export interface ViewUrl { 29 | segmentNumber: number; 30 | primariness: number; 31 | url: string; 32 | } 33 | -------------------------------------------------------------------------------- /web/src/interfaces/Maintainer.ts: -------------------------------------------------------------------------------- 1 | export interface Maintainer { 2 | id: number; 3 | name: string; 4 | url?: string; 5 | emailAddress?: string; 6 | twitterHandle?: string; 7 | filterListIds: number[]; 8 | } 9 | -------------------------------------------------------------------------------- /web/src/interfaces/Software.ts: -------------------------------------------------------------------------------- 1 | export interface Software { 2 | id: number; 3 | name: string; 4 | description?: string; 5 | homeUrl?: string; 6 | downloadUrl?: string; 7 | supportsAbpUrlScheme: boolean; 8 | syntaxIds: number[]; 9 | } 10 | -------------------------------------------------------------------------------- /web/src/interfaces/Syntax.ts: -------------------------------------------------------------------------------- 1 | export interface Syntax { 2 | id: number; 3 | name: string; 4 | description?: string; 5 | url?: string; 6 | filterListIds: number[]; 7 | softwareIds: number[]; 8 | } 9 | -------------------------------------------------------------------------------- /web/src/interfaces/Tag.ts: -------------------------------------------------------------------------------- 1 | export interface Tag { 2 | id: number; 3 | name: string; 4 | description?: string; 5 | filterListIds: number[]; 6 | } 7 | -------------------------------------------------------------------------------- /web/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /web/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from "web-vitals"; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /web/src/setupProxy.js: -------------------------------------------------------------------------------- 1 | const { createProxyMiddleware } = require("http-proxy-middleware"); 2 | 3 | module.exports = function (app) { 4 | app.use( 5 | "https://api.filterlists.com", 6 | createProxyMiddleware({ 7 | target: "http://localhost:8080", 8 | changeOrigin: true, 9 | }), 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /web/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import "@testing-library/jest-dom"; 6 | -------------------------------------------------------------------------------- /web/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { nameof } from "./nameof"; 2 | 3 | export { nameof }; 4 | -------------------------------------------------------------------------------- /web/src/utils/nameof.ts: -------------------------------------------------------------------------------- 1 | //https://stackoverflow.com/a/50470026/2343739 2 | export const nameof = (name: Extract) => name; 3 | -------------------------------------------------------------------------------- /web/staticwebapp.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "route": "/static/*", 5 | "headers": { 6 | "Cache-Control": "public, max-age=31536000, immutable" 7 | } 8 | }, 9 | { 10 | "route": "/favicon.ico", 11 | "headers": { 12 | "Cache-Control": "public, max-age=86400" 13 | } 14 | }, 15 | { 16 | "route": "/logo_filterlists.png", 17 | "headers": { 18 | "Cache-Control": "public, max-age=31536000, immutable" 19 | } 20 | }, 21 | { 22 | "route": "/icon_filterlists.png", 23 | "headers": { 24 | "Cache-Control": "public, max-age=31536000, immutable" 25 | } 26 | }, 27 | { 28 | "route": "/manifest.json", 29 | "headers": { 30 | "Cache-Control": "public, max-age=86400" 31 | } 32 | }, 33 | { 34 | "route": "/robots.txt", 35 | "headers": { 36 | "Cache-Control": "public, max-age=86400" 37 | } 38 | }, 39 | { 40 | "route": "/tpl-redirect.js", 41 | "headers": { 42 | "Cache-Control": "public, max-age=86400" 43 | } 44 | }, 45 | { 46 | "route": "/tpl.html", 47 | "headers": { 48 | "Cache-Control": "public, max-age=86400" 49 | } 50 | } 51 | ], 52 | "navigationFallback": { 53 | "rewrite": "/index.html" 54 | }, 55 | "trailingSlash": "never", 56 | "globalHeaders": { 57 | "Content-Security-Policy": "upgrade-insecure-requests; default-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'none'; connect-src 'self' https://api.filterlists.com; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; manifest-src 'self';", 58 | "Permissions-Policy": "accelerometer=(), ambient-light-sensor=(), attribution-reporting=(), autoplay=(), battery=(), bluetooth=(), browsing-topics=(), camera=(), compute-pressure=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), identity-credentials-get=(), idle-detection=(), local-fonts=(), magnetometer=(), microphone=(), midi=(), otp-credentials=(), payment=(), picture-in-picture=(), publickey-credentials-create=(), publickey-credentials-get=(), screen-wake-lock=(), serial=(), speaker-selection=(), storage-access=(), usb=(), web-share=(), window-management=(), xr-spatial-tracking=()" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"] 20 | } 21 | --------------------------------------------------------------------------------