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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions src/lib/data/picks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
picks:
- name: LibreWolf
reason: 'Good balance between increased privacy and usability.'

- name: Tor Browser
reason: 'Provides total anonymity. Use when you need the highest level of privacy.'

- name: DuckDuckGo
reason: 'Privacy-first search engine. Default for Mullvad, LibreWolf, and Tor browsers.'

- name: Brave Search
reason: 'Independent search index, unlike DDG which is based on Bing.'

- name: uBlock Origin
reason: 'Protects from ads, speeds up page load, prevents scripts, trackers, and malware.'

- name: Cryptomator
reason: 'Encrypts your files so cloud providers only see jibberish.'

- name: Syncthing
reason: 'Open source peer-2-peer continuous file syncing. Runs on all your devices.'

- name: Ente Photos
reason: 'End-to-end encrypted photo backup. Fully open source and self-hostable.'

- name: Obsidian
reason: 'Note-taking with local-first storage and markdown support. Can be combined with Syncthing'

- name: Bitwarden
reason: 'Free and open source. Liked by many users because it gives a lot for very cheap. Self-hostable.'

- name: ProtonPass
reason: 'Open source password manager that integrates with SimpleLogin for email aliases.'

- name: ProtonMail
reason: 'Supports OpenPGP encryption. Easy migration from Gmail with one-click forwarding.'

- name: Tuta
reason: 'Quantum computer safe encryption.'

- name: Mullvad
reason: 'Private signup, battle tested, audited. Swedish company with strong privacy stance.'

- name: ProtonVPN
reason: 'Free tier available, port forwarding, good for streaming. Lots of server options.'

- name: GrapheneOS
reason: 'Most secure and private mobile OS today.'

- name: Signal
reason: 'Good fit between privacy, security, and usability. Works without Google notification service.'
45 changes: 44 additions & 1 deletion src/lib/features/awesome-privacy/service.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import { load } from 'js-yaml';

import rawYaml from '../../data/awesome-privacy.yml?raw';
import rawPicks from '../../data/picks.yml?raw';

import type { AwesomePrivacyData, Category, SearchEntry, Section, Service } from './types';
import type { AwesomePrivacyData, Category, Pick, SearchEntry, Section, Service } from './types';

import { FEATURED_CATEGORIES } from '$lib/configs';

export class AwesomePrivacy {
readonly featuredCatgories = FEATURED_CATEGORIES;

private _data: AwesomePrivacyData = this._loadData();
private _picks: Map<string, string> = this._loadPicks();
private _searchIndex: SearchEntry[] | null = null;

constructor(featuredCategories?: string[]) {
if (featuredCategories) {
this.featuredCatgories = featuredCategories;
}
this._mergePicks();
}

/*
Expand All @@ -25,13 +28,53 @@ export class AwesomePrivacy {
return load(rawYaml) as AwesomePrivacyData;
}

/*
* Reads the picks YAML and returns a Map of service name to recommendation reason.
*/
private _loadPicks(): Map<string, string> {
const picksData = load(rawPicks) as { picks: Pick[] };
const picksMap = new Map<string, string>();

for (const pick of picksData.picks) {
if (pick.reason) {
picksMap.set(pick.name, pick.reason);
}
}

return picksMap;
}

/*
* Merges pick data into all services across all categories.
*/
private _mergePicks(): void {
for (const category of this._data.categories) {
for (const section of category.sections) {
for (const service of section.services) {
const reason = this._picks.get(service.name);
if (reason) {
service.recommended = true;
service.recommendationReason = reason;
}
}
}
}
}

/*
* Gets the entire Awesome Privacy data object.
*/
public getData(): AwesomePrivacyData {
return this._data;
}

/**
* Gets the list of all picks.
*/
public getPicks(): Pick[] {
return Array.from(this._picks.entries()).map(([name, reason]) => ({ name, reason }));
}

/**
* Slugifies a name by converting it to lowercase, replacing spaces with hyphens, and removing special characters.
*
Expand Down
7 changes: 7 additions & 0 deletions src/lib/features/awesome-privacy/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ export type ShortService = {
url: string;
};

export type Pick = {
name: string;
reason?: string;
};

export type Service = {
name: string;
description: string;
Expand All @@ -23,6 +28,8 @@ export type Service = {
androidApp?: string;
discordInvite?: string;
subreddit?: string;
recommended?: boolean;
recommendationReason?: string;
};

export type Section = {
Expand Down
Loading