Skip to content

Commit 32905d8

Browse files
somehow
1 parent bc413ea commit 32905d8

7 files changed

Lines changed: 2390 additions & 26 deletions

File tree

claude/activity.json

Lines changed: 805 additions & 0 deletions
Large diffs are not rendered by default.

claude/api.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
GET with accept application/json https://fetlife.com/Maddison75/activity?accurate_per_page=6 gives activity.json
2+
GET with accept application/json https://fetlife.com/inbox/load_more?offset=0&filter=inbox&order=newest gives

claude/inbox.json

Lines changed: 1389 additions & 0 deletions
Large diffs are not rendered by default.

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

source/content.css

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
1-
#text-notice {
2-
position: fixed;
3-
top: 10px;
4-
left: 10px;
5-
padding: 10px;
6-
background: #fff;
7-
z-index: 999999;
1+
.seen-indicator {
2+
display: inline-block;
3+
padding: 4px 8px;
4+
margin: 4px 0;
5+
font-size: 12px;
6+
font-weight: 500;
7+
border-radius: 4px;
8+
position: relative;
9+
}
10+
11+
.seen-indicator.seen {
12+
background-color: #e8f5e9;
13+
color: #2e7d32;
14+
border: 1px solid #4caf50;
15+
}
16+
17+
.seen-indicator.not-seen {
18+
background-color: #fff3e0;
19+
color: #e65100;
20+
border: 1px solid #ff9800;
821
}

source/content.js

Lines changed: 167 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,173 @@
1-
import optionsStorage from './options-storage.js';
1+
console.log('💌 "she ve seen it" loaded');
22

3-
console.log('💈 Content script loaded for', chrome.runtime.getManifest().name);
3+
// Fetch inbox conversations
4+
async function fetchInbox() {
5+
try {
6+
const response = await fetch('https://fetlife.com/api/v1/conversations');
7+
const data = await response.json();
8+
return data;
9+
} catch (error) {
10+
console.error('Error fetching inbox:', error);
11+
return null;
12+
}
13+
}
14+
15+
// Fetch user activity
16+
async function fetchUserActivity(userId) {
17+
try {
18+
const response = await fetch(`https://fetlife.com/api/v2/members/${userId}/feed`);
19+
const data = await response.json();
20+
return data;
21+
} catch (error) {
22+
console.error(`Error fetching activity for user ${userId}:`, error);
23+
return null;
24+
}
25+
}
26+
27+
// Determine if user has likely seen the message
28+
function hasSeenMessage(lastMessageTime, lastActivityTime) {
29+
// If user was active after the message was sent, they probably saw it
30+
return lastActivityTime > lastMessageTime;
31+
}
32+
33+
// Get the most recent activity timestamp from user's feed
34+
function getLastActivityTime(activityData) {
35+
if (!activityData || !activityData.story_groups || activityData.story_groups.length === 0) {
36+
return 0;
37+
}
38+
39+
// Get the timestamp from the first story (most recent)
40+
const firstStory = activityData.story_groups[0].stories[0];
41+
return firstStory.timestamp / 1000; // Convert microseconds to seconds
42+
}
43+
44+
// Main function to process conversations
45+
async function processConversations() {
46+
const inboxData = await fetchInbox();
47+
48+
if (!inboxData || !inboxData.conversations) {
49+
console.error('No inbox data available');
50+
return;
51+
}
52+
53+
const results = [];
54+
55+
for (const conversation of inboxData.conversations) {
56+
// Check if the last message was sent by the current user
57+
const currentUserId = conversation.you.id;
58+
const lastMessage = conversation.last_message;
59+
60+
if (lastMessage.author.id === currentUserId) {
61+
// Get the other user(s) in the conversation
62+
const otherUsers = conversation.with_users;
63+
64+
for (const otherUser of otherUsers) {
65+
const activityData = await fetchUserActivity(otherUser.id);
66+
const lastActivityTime = getLastActivityTime(activityData);
67+
const lastMessageTime = lastMessage.created_at;
68+
69+
const seen = hasSeenMessage(lastMessageTime, lastActivityTime);
70+
71+
results.push({
72+
conversationId: conversation.conversation_id,
73+
userId: otherUser.id,
74+
nickname: otherUser.nickname,
75+
lastMessageTime,
76+
lastActivityTime,
77+
probablySeen: seen
78+
});
79+
}
80+
}
81+
}
82+
83+
return results;
84+
}
85+
86+
// Add visual indicators to the inbox page
87+
async function addIndicators() {
88+
const results = await processConversations();
89+
90+
if (!results) {
91+
return;
92+
}
93+
94+
// Wait for the inbox UI to be ready
95+
await waitForElement('[data-component="Conversations"]');
96+
97+
// Add indicators for each conversation
98+
for (const result of results) {
99+
addIndicatorToConversation(result);
100+
}
101+
}
102+
103+
// Wait for an element to exist in the DOM
104+
function waitForElement(selector, timeout = 10000) {
105+
return new Promise((resolve, reject) => {
106+
const element = document.querySelector(selector);
107+
if (element) {
108+
resolve(element);
109+
return;
110+
}
111+
112+
const observer = new MutationObserver(() => {
113+
const element = document.querySelector(selector);
114+
if (element) {
115+
observer.disconnect();
116+
resolve(element);
117+
}
118+
});
119+
120+
observer.observe(document.body, {
121+
childList: true,
122+
subtree: true
123+
});
124+
125+
setTimeout(() => {
126+
observer.disconnect();
127+
reject(new Error('Element not found within timeout'));
128+
}, timeout);
129+
});
130+
}
131+
132+
// Add indicator to a specific conversation
133+
function addIndicatorToConversation(result) {
134+
// Find the conversation element by conversation ID
135+
const conversationLinks = document.querySelectorAll('a[href*="/conversations/"]');
136+
137+
for (const link of conversationLinks) {
138+
if (link.href.includes(`/conversations/${result.conversationId}`)) {
139+
// Find a suitable place to add the indicator
140+
const parent = link.closest('li') || link.closest('div[class*="conversation"]');
141+
142+
if (parent && !parent.querySelector('.seen-indicator')) {
143+
const indicator = document.createElement('div');
144+
indicator.className = 'seen-indicator';
145+
146+
if (result.probablySeen) {
147+
indicator.classList.add('seen');
148+
indicator.innerHTML = '👁️ Probably seen';
149+
indicator.title = `Last active: ${new Date(result.lastActivityTime * 1000).toLocaleString()}`;
150+
} else {
151+
indicator.classList.add('not-seen');
152+
indicator.innerHTML = '⏱️ Probably not seen';
153+
indicator.title = `Last active: ${new Date(result.lastActivityTime * 1000).toLocaleString()}`;
154+
}
155+
156+
parent.appendChild(indicator);
157+
}
158+
159+
break;
160+
}
161+
}
162+
}
4163

164+
// Initialize
5165
async function init() {
6-
const options = await optionsStorage.getAll();
7-
const color = `rgb(${options.colorRed}, ${options.colorGreen},${options.colorBlue})`;
8-
const text = options.text;
9-
const notice = document.createElement('div');
10-
notice.innerHTML = text;
11-
document.body.prepend(notice);
12-
notice.id = 'text-notice';
13-
notice.style.border = '2px solid ' + color;
14-
notice.style.color = color;
166+
// Check if we're on the inbox page
167+
if (window.location.pathname === '/inbox' || window.location.pathname.startsWith('/inbox')) {
168+
console.log('Processing inbox...');
169+
await addIndicators();
170+
}
15171
}
16172

17173
init();

source/manifest.json

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
{
22
"$schema": "https://json.schemastore.org/chrome-manifest",
3-
"name": "Awesome Extension",
4-
"version": "0.0.0",
5-
"description": "An awesome new browser extension",
6-
"homepage_url": "https://github.com/fregante/browser-extension-template",
3+
"name": "she ve seen it",
4+
"version": "0.0.1",
5+
"description": "Shows if users have likely seen your FetLife messages based on their activity",
76
"manifest_version": 3,
87
"minimum_chrome_version": "121",
98
"browser_specific_settings": {
109
"gecko": {
11-
"id": "awesome-extension@notlmn.github.io",
10+
"id": "she-ve-seen-it@notlmn.github.io",
1211
"strict_min_version": "109.0"
1312
}
1413
},
@@ -19,11 +18,11 @@
1918
"storage"
2019
],
2120
"host_permissions": [
22-
"https://github.com/*"
21+
"https://fetlife.com/*"
2322
],
2423
"content_scripts": [
2524
{
26-
"matches": [ "https://github.com/fregante/browser-extension-template/*" ],
25+
"matches": [ "https://fetlife.com/inbox*" ],
2726
"js": [ "content.js" ],
2827
"css": [ "content.css" ],
2928
"run_at": "document_end"

0 commit comments

Comments
 (0)