This is a submission for the DEV Weekend Challenge: Community
This is the fastest I've ever made something functional.
- b r e a t h e s -
The Community
For the past couple years, I have actively used dev as a resource. Learning, discovering, and trying things way out of my league. Because of this, I have learned more than I would have on my own with no other support. When you enter the realm of web development and you don't really have anyone close to you doing it, it's the wild west. Dev has provided a place for all walks of life to learn and push forward to discover new things. For this reason, I chose Dev as my community!
The one thing I noticed about dev is there there was no p2p chat. I thought it would be cool if users could send simple messages together about cool projects, ect. If the chat is implemented by way of an optional extension, then we are never forcing people to deal with the chat. It's truly a personal decision.
Thinking from a community standpoint - I wanted to include all the meme monday memes inside the gif picker in the chat. YES you can probably find your meme you posted 2 months ago. This way, I'm using community data, for the community, to continue enhancing the experience.
What I Built
A free open source messaging app for dev members to communicate with each other without having to share personal information. It's shipped as an extension but has a pop-out option for ease of use.
Demo
Code
AnnaVi11arrea1
/
dev_messages
3rd party messaging app that works with Dev.to
Dev.to Messages (Unofficial 3rd Party Extension)
A Chrome extension that adds private direct messaging to Dev.to, with built-in link safety, spam reporting, and a first-contact approval system.
File Structure
dev_messages/
βββ manifest.json MV3 manifest
βββ background.js Service worker β updates unread badge
βββ content.js Injected into dev.to β detects user, adds Message buttons
βββ popup.html All UI views & modals
βββ popup.css Dev.to-inspired styles (380px popup)
βββ popup.js Full SPA app logic
βββ js/
β βββ storage.js chrome.storage.local wrapper
β βββ eligibility.js Dev.to API eligibility check
β βββ linkSafety.js URL spoofing detection + safe renderer
βββ icons/ 16 / 48 / 128 px PNG icons
Features
| Feature | Details |
|---|---|
| Account eligibility | Checks joined_at & articles_count via the Dev.to public API. Account must be at least 30 days old and have at least 1 published post before sending or receiving messages. |
| First-contact approval | When someone messages you for the first time, |
Meme Search Implementation
Arguably, the most important feature. XD
I wanted not to just pull in the memes, but to allow a user to search them. The best way I figured was to use the alt tags as search parameters. Many of us are nice enough to put alt tags on our stuff, and so I was able to leverage that to get a sort of lazy search going. Hey, it works. I thought that was a little cool and different so that is the snippet I'll be sharing below:
/* gifPicker.js β Fetches meme images from Ben Halpern's Meme Monday posts,
including every image posted in the comments (community memes). */
const GifPicker = {
_cache: null,
async fetchImages() {
if (this._cache) return this._cache;
/* ββ Step 1: collect all Meme Monday articles βββββββββββββββββββββββββββ */
const articles = [];
for (let page = 1; page <= 6; page++) {
try {
const res = await fetch(
`https://dev.to/api/articles?username=ben&per_page=100&page=${page}`
);
if (!res.ok) break;
const data = await res.json();
if (!data.length) break;
for (const a of data) {
if (/meme\s+monday/i.test(a.title)) articles.push(a);
}
if (data.length < 100) break;
} catch { break; }
}
/* ββ Step 2: collect images with dedup ββββββββββββββββββββββββββββββββββ */
const seen = new Set();
const images = [];
const add = (url, title) => {
if (!url || seen.has(url)) return;
seen.add(url);
images.push({ url, title });
};
/* Cover images first so they appear at the top of the grid */
for (const a of articles) {
if (a.cover_image) add(a.cover_image, a.title);
}
/* ββ Step 3: fetch all comment threads in parallel ββββββββββββββββββββββ */
const commentThreads = await Promise.all(
articles.map(a =>
fetch(`https://dev.to/api/comments?a_id=${a.id}`)
.then(r => r.ok ? r.json() : [])
.catch(() => [])
)
);
for (let i = 0; i < articles.length; i++) {
this._extractImages(commentThreads[i], articles[i].title, add);
}
this._cache = images;
return images;
},
/* Recursively walk comment tree and pull every <img src="β¦"> with its own alt */
_extractImages(comments, articleTitle, add) {
for (const comment of comments) {
const imgTags = (comment.body_html || '').matchAll(/<img([^>]+)>/gi);
for (const m of imgTags) {
const attrs = m[1];
const srcM = attrs.match(/src="([^"]+)"/i);
const altM = attrs.match(/alt="([^"]*)"/i);
if (srcM) {
const alt = altM?.[1]?.trim() || articleTitle;
add(srcM[1], alt);
}
}
if (comment.children?.length) {
this._extractImages(comment.children, articleTitle, add);
}
}
},
};
How I Built It
I very quickly realized that I was going to need a secure database so users could keep their chat history. I'm using Neon's free tier for this project. Give it a whirl! XD. Requests from the extension go to Vercel over HTTPS. Vercel connects to Neon with an encrypted connection. The database is never exposed to the internet, and it is only queried by Vercel's serverless functions.
I used copilot CLI to quickly build up a working template - I would not be able to build something so quickly without it. This is not my first chrome extension, but it is the 'fanciest' one I have made to date.
I tested with my unpacked files in the google chrome extensions. I was lucky enough to test functionality with another dev user. That was amazingly helpful. A big thank you to @trickell for being awesome.
I had fun making a settings and options area for users which includes colors and text sizes for increased accessibility.
Features:
Created with the mindset of reducing spam and annoyances
Account eligibility - Checks
joined_at&articles_countvia the Dev.to public API. Account must be at least 30 days old and have at least 1 published post before sending or receiving messages.First-contact approval - When someone messages you for the first time, you see a banner with their opening message and must hit Approve or Deny before the conversation unlocks.
Flag / report spam - Every incoming message has a π© button. Clicking it opens a reason picker (spam, phishing, harassment, inappropriate, other) and marks the message as reported.
Link spoofing detection - Before any link opens,
LinkSafety.isSpoofed()compares the visible URL text against the actualhrefhostname. A red alert is shown if they differ.Link safety warning - Every link in every message routes through a warning modal that displays the full destination URL before opening it in a new tab.
Local storage persistence - All conversations, approvals, flags, and user data are stored in
chrome.storage.local. The extension must be installed and active to read messages.Unread badge - The extension icon shows a live unread count, updated by the background service worker whenever storage changes
My extension is getting reviewed by google currently... whish me luck! haha
A user must have the extension to test it. Guess I've made myself a guinea pig haven't I. (hides, π )



Top comments (6)
Glad you made this project and was glad I was not the only one about p2p functionality missing on Dev.to! I would hope we get a some sort of "Mail Inbox" as a feature for the Forem website since it can be useful to private message someone instead of relying on going to a random post and comment on there (which happened to me).
Thanks for sharing and great work!
Thank you so much! β¨οΈ
I feel like it could encourage friendships, professional connections, and collaborators. I like the idea of an inbox to!
Haha! π€£ Collecting memes from Meme Monday is definitely what all DEV Community users want. Also, this app has robust security. Very cool app!
Thank you! That means alot!
This is a really cool project. I've been thinking about making one myself, but haven't had the time. Thank you for your efforts and for uploading it. I wish you continued success.
Thank you!