A community-maintained directory of Bluesky Personal Data Servers (PDS). pdslist.wisp.place

Initial commit

Daniel Morrisey 611f3287

+611
+137
README.md
··· 1 + # Bluesky PDS Directory 2 + 3 + **WILL MOVE TO Tangled SOON *tm*** 4 + 5 + A community-maintained directory of Bluesky Personal Data Servers *(PDS)*. 6 + 7 + ## JSON Structure 8 + 9 + The `pdslist.json` file contains an array of PDS server objects. Each object has the following structure: 10 + 11 + ```json 12 + { 13 + "url": "https://ypds.example.com", 14 + "supportedHandles": ["*.example.com", "*.example.net"], 15 + "maintainer": "@your-handle.example.com", 16 + "tosUrl": "https://pds.example.com/terms", 17 + "privacyUrl": "https://pds.example.com/privacy", 18 + "inviteCodeRequired": false 19 + } 20 + ``` 21 + 22 + ### Field Descriptions 23 + 24 + | Field | Type | Required | Description | 25 + |-------|------|----------|-------------| 26 + | `url` | string | ✅ Yes | The base URL of your PDS server (must include https://) | 27 + | `supportedHandles` | array of strings | ✅ Yes | Domain patterns for handles your PDS supports (e.g., `*.example.com`) | 28 + | `maintainer` | string | ✅ Yes | Bluesky handle of the server maintainer (format: `@handle.domain.com`) | 29 + | `contactEmail` | string | ⚠️ Optional | Contact email for the PDS administrator | 30 + | `tosUrl` | string | ⚠️ Optional | URL to your Terms of Service page | 31 + | `privacyUrl` | string | ⚠️ Optional | URL to your Privacy Policy page | 32 + | `inviteCodeRequired` | boolean | ✅ Yes | Whether new users need an invite code to join (`true` or `false`) | 33 + 34 + ### Example Entry 35 + 36 + ```json 37 + { 38 + "url": "https://pds.myserver.com", 39 + "supportedHandles": [ 40 + "*.myserver.com", 41 + "*.myserver.org" 42 + ], 43 + "maintainer": "@admin.myserver.com", 44 + "contactEmail": "admin@myserver.com", 45 + "tosUrl": "https://pds.myserver.com/terms-of-service", 46 + "privacyUrl": "https://pds.myserver.com/privacy-policy", 47 + "inviteCodeRequired": true 48 + } 49 + ``` 50 + 51 + ## Adding Your PDS 52 + 53 + We welcome additions to the directory! There are two ways to add your PDS: 54 + 55 + ### Option 1: Submit a GitHub Issue 56 + 57 + 1. Go to the [Issues](../../issues) page 58 + 2. Click "New Issue" 59 + 3. Add your PDS information: 60 + - PDS URL 61 + - Supported handles 62 + - Your maintainer handle 63 + - Contact email (optional) 64 + - Terms of Service URL (if available) 65 + - Privacy Policy URL (if available) 66 + - Whether invite codes are required 67 + 4. Submit the issue 68 + 69 + A maintainer will review your submission and add it to the list. 70 + 71 + ### Option 2: Submit a Pull Request 72 + 73 + 1. Fork this repository 74 + 2. Edit `pdslist.json` 75 + 3. Add your PDS entry to the array following the structure above 76 + 4. Ensure your JSON is valid (use a JSON validator) 77 + 5. Commit your changes with a clear message: `Add [your-pds-name] to directory` 78 + 6. Create a Pull Request with: 79 + - A clear title: "Add [your PDS name]" 80 + - Description of your PDS 81 + - Confirmation that you maintain the server 82 + 83 + ### Submission Guidelines 84 + 85 + ✅ **Do:** 86 + - Use valid JSON formatting 87 + - Include all required fields 88 + - Use HTTPS URLs only 89 + - Provide accurate information 90 + - Test your PDS is accessible before submitting 91 + 92 + ❌ **Don't:** 93 + - Submit inactive or offline servers 94 + - Include test or development servers 95 + - Use HTTP (non-secure) URLs 96 + - Submit duplicate entries 97 + 98 + ## Review Process 99 + 100 + All submissions are reviewed by maintainers to ensure: 101 + - JSON is properly formatted 102 + - URLs are accessible and valid 103 + - Information is accurate 104 + - The PDS is actively maintained 105 + - Terms and privacy policies exist (if links provided) 106 + 107 + ## Updating Your Entry 108 + 109 + If you need to update your PDS information: 110 + 111 + 1. Submit an issue with "Update [your-pds-name]" as the title 112 + 2. Or create a Pull Request with the updated information 113 + 114 + ## Removing Your Entry 115 + 116 + To remove your PDS from the directory: 117 + 118 + 1. Submit an issue with "Remove [your-pds-name]" as the title 119 + 2. Or create a Pull Request removing your entry 120 + 121 + ## Code of Conduct 122 + 123 + Please be respectful and professional in all interactions. This is a community resource for everyone. 124 + 125 + ## License 126 + 127 + This project is open source and available for anyone to use and contribute to. 128 + 129 + ## Support 130 + 131 + For questions or issues: 132 + - Open a GitHub Issue 133 + - Check existing issues for similar questions 134 + 135 + --- 136 + 137 + **Note:** This is a community-maintained list. We cannot guarantee the availability, security, or policies of listed servers. Always review a server's terms and privacy policy before joining.
+121
app.js
··· 1 + // Load PDS data and populate table 2 + document.addEventListener('DOMContentLoaded', function() { 3 + loadPDSData(); 4 + }); 5 + 6 + async function loadPDSData() { 7 + const loading = document.getElementById('loading'); 8 + const content = document.getElementById('content'); 9 + const tableBody = document.getElementById('table-body'); 10 + const noData = document.getElementById('no-data'); 11 + const serverCount = document.getElementById('server-count'); 12 + 13 + try { 14 + const response = await fetch('./pdslist.json'); 15 + const pdsData = await response.json(); 16 + 17 + // Hide loading 18 + loading.style.display = 'none'; 19 + content.style.display = 'block'; 20 + 21 + if (pdsData.length === 0) { 22 + noData.style.display = 'block'; 23 + return; 24 + } 25 + 26 + // Update server count 27 + serverCount.textContent = pdsData.length; 28 + 29 + // Populate table 30 + pdsData.forEach(pds => { 31 + const row = createTableRow(pds); 32 + tableBody.appendChild(row); 33 + }); 34 + 35 + } catch (error) { 36 + console.error('Error loading PDS data:', error); 37 + loading.innerHTML = '<p style="color: red;">Error loading database. Please check console.</p>'; 38 + } 39 + } 40 + 41 + function createTableRow(pds) { 42 + const row = document.createElement('tr'); 43 + 44 + // URL 45 + const urlCell = document.createElement('td'); 46 + const urlLink = document.createElement('a'); 47 + urlLink.href = pds.url; 48 + urlLink.target = '_blank'; 49 + urlLink.textContent = pds.url; 50 + urlCell.appendChild(urlLink); 51 + row.appendChild(urlCell); 52 + 53 + // Handles 54 + const handlesCell = document.createElement('td'); 55 + if (pds.supportedHandles && pds.supportedHandles.length > 0) { 56 + handlesCell.textContent = pds.supportedHandles.join(', '); 57 + } else { 58 + handlesCell.textContent = 'N/A'; 59 + } 60 + row.appendChild(handlesCell); 61 + 62 + // Maintainer 63 + const maintainerCell = document.createElement('td'); 64 + if (pds.maintainer) { 65 + const link = document.createElement('a'); 66 + link.href = `https://madebydanny.uk/followonbsky?did=${pds.maintainer}`; 67 + link.target = '_blank'; 68 + link.textContent = pds.maintainer; 69 + maintainerCell.appendChild(link); 70 + } else { 71 + maintainerCell.textContent = '—'; 72 + } 73 + row.appendChild(maintainerCell); 74 + 75 + // Contact Email 76 + const emailCell = document.createElement('td'); 77 + if (pds.contactEmail) { 78 + const link = document.createElement('a'); 79 + link.href = `mailto:${pds.contactEmail}`; 80 + link.textContent = pds.contactEmail; 81 + emailCell.appendChild(link); 82 + } else { 83 + emailCell.textContent = '—'; 84 + } 85 + row.appendChild(emailCell); 86 + 87 + // Invite Code Required 88 + const inviteCell = document.createElement('td'); 89 + inviteCell.textContent = pds.inviteCodeRequired ? 'Yes' : 'No'; 90 + row.appendChild(inviteCell); 91 + 92 + // Terms of Service 93 + const tosCell = document.createElement('td'); 94 + if (pds.tosUrl) { 95 + const link = document.createElement('a'); 96 + link.href = pds.tosUrl; 97 + link.target = '_blank'; 98 + link.rel = 'noopener noreferrer'; 99 + link.textContent = 'Link'; 100 + tosCell.appendChild(link); 101 + } else { 102 + tosCell.textContent = '—'; 103 + } 104 + row.appendChild(tosCell); 105 + 106 + // Privacy Policy 107 + const privacyCell = document.createElement('td'); 108 + if (pds.privacyUrl) { 109 + const link = document.createElement('a'); 110 + link.href = pds.privacyUrl; 111 + link.target = '_blank'; 112 + link.rel = 'noopener noreferrer'; 113 + link.textContent = 'Link'; 114 + privacyCell.appendChild(link); 115 + } else { 116 + privacyCell.textContent = '—'; 117 + } 118 + row.appendChild(privacyCell); 119 + 120 + return row; 121 + }
+44
index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <title>Bluesky PDS Directory</title> 7 + </head> 8 + <body> 9 + <h1>Bluesky PDS Directory</h1> 10 + <p>Community-maintained database of Personal Data Servers</p> 11 + <p><b>NOTE: Data on this site is sourced from <a href="https://pdsls.dev">PDSls</a></b></p> 12 + <p><a href="https://github.com/therealfuntimeswithdanny/pdslist?tab=readme-ov-file#adding-your-pds">Add a PDS</a></p> 13 + 14 + <div id="loading"> 15 + <p>Loading database...</p> 16 + </div> 17 + 18 + <div id="content" style="display: none;"> 19 + <p>Total Servers: <strong id="server-count">0</strong></p> 20 + 21 + <table id="pds-table" border="1" cellpadding="10" cellspacing="0"> 22 + <thead> 23 + <tr> 24 + <th>URL</th> 25 + <th>Handles</th> 26 + <th>Maintainer</th> 27 + <th>Email</th> 28 + <th>Invite Code Required</th> 29 + <th>Terms</th> 30 + <th>Privacy</th> 31 + </tr> 32 + </thead> 33 + <tbody id="table-body"> 34 + </tbody> 35 + </table> 36 + 37 + <div id="no-data" style="display: none;"> 38 + <p>No PDS servers found.</p> 39 + </div> 40 + </div> 41 + 42 + <script src="app.js"></script> 43 + </body> 44 + </html>
+129
pdslist.json
··· 1 + [ 2 + { 3 + "url": "https://bsky.social", 4 + "supportedHandles": [ 5 + "*.bsky.social" 6 + ], 7 + "maintainer": "@bsky.app", 8 + "tosUrl": "https://bsky.social/about/support/tos", 9 + "privacyUrl": "https://bsky.social/about/support/privacy-policy", 10 + "inviteCodeRequired": false 11 + }, 12 + { 13 + "url": "https://pds.madebydanny.uk", 14 + "supportedHandles": [ 15 + ".pds.madebydanny.uk", 16 + ".pds.danielmorrisey.com", 17 + ".good-example.com", 18 + ".mbdio.uk.", 19 + "certifiedshitposter.com" 20 + ], 21 + "contactEmail": "danielmorrisey@pm.me", 22 + "maintainer": "@madebydanny.uk", 23 + "tosUrl": "https://pds.madebydanny.uk/about/terms.html", 24 + "privacyUrl": "https://pds.madebydanny.uk/about/privacy.html", 25 + "inviteCodeRequired": false 26 + }, 27 + { 28 + "url": "https://blacksky.app", 29 + "supportedHandles": [ 30 + ".myatproto.social", 31 + ".blacksky.app", 32 + ".cryptoanarchy.network" 33 + ], 34 + "contactEmail": "support@blacksky.app", 35 + "maintainer": "@blackskyweb.xyz", 36 + "privacyUrl": "https://blackskyweb.xyz/about/support/privacy-policy/", 37 + "tosUrl": "https://blackskyweb.xyz/about/support/tos/", 38 + "inviteCodeRequired": false 39 + }, 40 + { 41 + "url": "https://selfhosted.social", 42 + "supportedHandles": [ 43 + ".selfhosted.social" 44 + ], 45 + "contactEmail": "modmail@selfhosted.social", 46 + "maintainer": "@baileytownsend.dev", 47 + "privacyUrl": "https://selfhosted.social/legal#privacy-policy", 48 + "tosUrl": "https://selfhosted.social/legal#terms-of-service", 49 + "inviteCodeRequired": false 50 + }, 51 + { 52 + "url": "https://altq.net", 53 + "supportedHandles": [ 54 + ".altq.net" 55 + ], 56 + "maintainer": "@fry69.dev", 57 + "inviteCodeRequired": "true" 58 + }, 59 + { 60 + "url": "https://tngl.sh", 61 + "supportedHandles": [ 62 + ".tngl.sh" 63 + ], 64 + "maintainer": "@tangled.org", 65 + "inviteCodeRequired": "true" 66 + }, 67 + { 68 + "url": "https://pds.tgirl.cloud", 69 + "supportedHandles": [ 70 + ".tgirl.beauty" 71 + ], 72 + "maintainer": "tgirl.cloud", 73 + "inviteCodeRequired": "true" 74 + }, 75 + { 76 + "url": "https://pds.witchcraft.systems", 77 + "supportedHandles": [ 78 + ".pds.witchcraft.systems" 79 + ], 80 + "maintainer": "@witchcraft.systems", 81 + "inviteCodeRequired": "true" 82 + }, 83 + { 84 + "url": "https://evil.gay", 85 + "supportedHandles": [ 86 + ".evil.gay", 87 + ".pds.mmatt.net" 88 + ], 89 + "maintainer": "@mmatt.net", 90 + "inviteCodeRequired": "true" 91 + }, 92 + { 93 + "url": "https://totallynotseth.dev", 94 + "supportedHandles": [ 95 + ".totallynotseth.dev" 96 + ], 97 + "maintainer": "@totallynotseth.dev", 98 + "inviteCodeRequired": "true" 99 + }, 100 + { 101 + "url": "https://pds.atpota.to", 102 + "supportedHandles": [ 103 + ".flush.es", 104 + ".on.anisota.net" 105 + ], 106 + "maintainer": "@dame.is", 107 + "inviteCodeRequired": "true" 108 + }, 109 + { 110 + "url": "https://katproto.girlonthemoon.xyz", 111 + "supportedHandles": [ 112 + ".katproto.girlonthemoon.xyz" 113 + ], 114 + "maintainer": "@katproto.girlonthemoon.xyz", 115 + "contactEmail": "witnesslachesis@disroot.org", 116 + "inviteCodeRequired": "true" 117 + }, 118 + { 119 + "url": "https://pds.tophhie.cloud", 120 + "supportedHandles": [ 121 + ".tophhie.social" 122 + ], 123 + "contactEmail": "help@tophhie.cloud", 124 + "maintainer": "@tophhie.cloud", 125 + "tosUrl": "https://blog.tophhie.cloud/atproto-tos/", 126 + "privacyUrl": "https://blog.tophhie.cloud/atproto-privacy-policy/", 127 + "inviteCodeRequired": false 128 + } 129 + ]
+180
styles.css
··· 1 + * { 2 + margin: 0; 3 + padding: 0; 4 + box-sizing: border-box; 5 + } 6 + 7 + body { 8 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; 9 + background-color: #f5f5f5; 10 + color: #333; 11 + line-height: 1.6; 12 + } 13 + 14 + .container { 15 + max-width: 1200px; 16 + margin: 0 auto; 17 + padding: 20px; 18 + } 19 + 20 + header { 21 + background-color: white; 22 + padding: 30px 0; 23 + margin-bottom: 30px; 24 + border-bottom: 2px solid #e0e0e0; 25 + } 26 + 27 + h1 { 28 + font-size: 32px; 29 + margin-bottom: 10px; 30 + } 31 + 32 + .subtitle { 33 + color: #666; 34 + font-size: 16px; 35 + margin-bottom: 20px; 36 + } 37 + 38 + .header-links { 39 + display: flex; 40 + gap: 15px; 41 + } 42 + 43 + .header-link { 44 + color: #0066cc; 45 + text-decoration: none; 46 + font-size: 14px; 47 + } 48 + 49 + .header-link:hover { 50 + text-decoration: underline; 51 + } 52 + 53 + .loading { 54 + text-align: center; 55 + padding: 40px; 56 + } 57 + 58 + .spinner { 59 + border: 4px solid #e0e0e0; 60 + border-top: 4px solid #0066cc; 61 + border-radius: 50%; 62 + width: 40px; 63 + height: 40px; 64 + animation: spin 1s linear infinite; 65 + margin: 0 auto 20px; 66 + } 67 + 68 + @keyframes spin { 69 + 0% { transform: rotate(0deg); } 70 + 100% { transform: rotate(360deg); } 71 + } 72 + 73 + .content { 74 + background-color: white; 75 + padding: 20px; 76 + border-radius: 4px; 77 + } 78 + 79 + .content.hidden, 80 + .no-data.hidden, 81 + #loading.hidden { 82 + display: none; 83 + } 84 + 85 + .stats { 86 + margin-bottom: 20px; 87 + padding-bottom: 20px; 88 + border-bottom: 1px solid #e0e0e0; 89 + } 90 + 91 + .stat-item { 92 + font-size: 16px; 93 + } 94 + 95 + .stat-item strong { 96 + font-weight: 600; 97 + } 98 + 99 + .pds-table { 100 + width: 100%; 101 + border-collapse: collapse; 102 + margin-top: 20px; 103 + } 104 + 105 + .pds-table thead { 106 + background-color: #f9f9f9; 107 + border-bottom: 2px solid #e0e0e0; 108 + } 109 + 110 + .pds-table th { 111 + text-align: left; 112 + padding: 15px; 113 + font-weight: 600; 114 + font-size: 14px; 115 + text-transform: uppercase; 116 + color: #666; 117 + } 118 + 119 + .pds-table td { 120 + padding: 12px 15px; 121 + border-bottom: 1px solid #e0e0e0; 122 + font-size: 14px; 123 + vertical-align: top; 124 + } 125 + 126 + .pds-table tbody tr:hover { 127 + background-color: #f9f9f9; 128 + } 129 + 130 + .pds-table a { 131 + color: #0066cc; 132 + text-decoration: none; 133 + word-break: break-all; 134 + } 135 + 136 + .pds-table a:hover { 137 + text-decoration: underline; 138 + } 139 + 140 + .handles-container { 141 + display: flex; 142 + flex-wrap: wrap; 143 + gap: 6px; 144 + } 145 + 146 + .handle-tag { 147 + background-color: #e8f0ff; 148 + color: #0066cc; 149 + padding: 4px 8px; 150 + border-radius: 3px; 151 + font-size: 12px; 152 + white-space: nowrap; 153 + border: 1px solid #cce0ff; 154 + } 155 + 156 + .link-button { 157 + display: inline-block; 158 + background-color: #0066cc; 159 + color: white; 160 + padding: 6px 12px; 161 + border-radius: 3px; 162 + font-size: 12px; 163 + text-decoration: none; 164 + transition: background-color 0.2s; 165 + } 166 + 167 + .link-button:hover { 168 + background-color: #0052a3; 169 + text-decoration: none; 170 + } 171 + 172 + .no-data { 173 + text-align: center; 174 + padding: 40px; 175 + color: #999; 176 + } 177 + 178 + .hidden { 179 + display: none; 180 + }