Skip to content

Commit

Permalink
nostr following feature
Browse files Browse the repository at this point in the history
  • Loading branch information
dhalsim committed Sep 5, 2024
1 parent 29b5128 commit 2327e68
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 87 deletions.
16 changes: 13 additions & 3 deletions src/helpers/nostr.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ function toFollowSet(event) {
);
}

export function getFollowSet({ pubkey, relays, callback }) {
export function getFollowSet({ pubkey, relays, callback, timeout }) {
const cache = getFollowSetFromCache(pubkey);

let since;
Expand All @@ -172,6 +172,7 @@ export function getFollowSet({ pubkey, relays, callback }) {
},
],
{
maxWait: timeout,
onevent(event) {
if (!since || event.created_at > since) {
const followSet = toFollowSet(event);
Expand All @@ -193,10 +194,11 @@ async function updateFollowList({ pubkey, tags, relays, log, accountPubkey }) {
pubkey,
tags,
content: "",
created_at: Math.floor(Date.now() / 1000),
};

const signedEvent = await requestSigningFromNip07({
type: "nostr-sign-event",
type: "nip07-sign-request",
from: "nostrize",
eventTemplate,
});
Expand All @@ -208,12 +210,20 @@ async function updateFollowList({ pubkey, tags, relays, log, accountPubkey }) {
const publishedCount = res.filter((r) => r.status === "fulfilled").length;
const publishFailedCount = res.filter((r) => r.status === "rejected").length;

log(accountPubkey, publishedCount, publishFailedCount);
log(
`accountPubkey: ${accountPubkey}, publishedCount: ${publishedCount}, publishFailedCount: ${publishFailedCount}`,
);

if (publishedCount === 0) {
throw new Error("failed to publish event");
}

const followSet = toFollowSet(signedEvent);

setFollowSetToCache(pubkey, signedEvent, followSet);

log(`followSet: cache is updated`);

return { followSet, latestEvent: signedEvent };
}

Expand Down
148 changes: 70 additions & 78 deletions src/twitter/profile/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ import { getLnurlData } from "../../helpers/lnurl.js";
import { lightsatsModalComponent } from "../../components/lightsats/lightsats-modal.js";
import { wrapInputTooltip } from "../../components/tooltip/tooltip-wrapper.js";

import { createTwitterButton } from "./twitter-helpers.js";

// Watch user handle for changes
// AccountName is from the URL
import { createTwitterButton, updateFollowButton } from "./twitter-helpers.js";

async function twitterProfilePage() {
const settings = await getLocalSettings();
Expand All @@ -45,6 +42,7 @@ async function twitterProfilePage() {
gui.gebid("n-tw-lightsats-button")?.remove();
gui.gebid("n-follows-you-indicator")?.remove();
gui.gebid("n-tw-nostr-profile-button")?.remove();
gui.gebid("n-tw-follow-unfollow-button")?.remove();
};

removeNostrButtons();
Expand Down Expand Up @@ -116,12 +114,18 @@ async function twitterProfilePage() {
settings.lightsatsSettings.enabled &&
settings.lightsatsSettings.apiKey
) {
createTwitterButton(buttonTobeCloned, accountName, {
const lightsatsButton = wrapInputTooltip({
id: "n-tw-lightsats-button",
icon: "💸",
modalComponentFn: () =>
lightsatsModalComponent({ user: accountName, settings }),
input: createTwitterButton(buttonTobeCloned, accountName, {
id: "n-tw-lightsats-button",
emojiIcon: "💸",
modalComponentFn: () =>
lightsatsModalComponent({ user: accountName, settings }),
}),
tooltipText: `Orange pill ${accountName} with Lightning Network`,
});

gui.insertAfter(lightsatsButton, buttonTobeCloned);
}

return;
Expand Down Expand Up @@ -155,17 +159,15 @@ async function twitterProfilePage() {
relays: nip07Relays,
});

// handle container is the div that contains the username and the handle
const usernamePanel = document.querySelector("div[data-testid='UserName']");

const handleContainer =
usernamePanel?.childNodes[0]?.childNodes[0]?.childNodes[0]?.childNodes[1];

const handle = handleContainer?.childNodes[0];

const handleContent = handle.textContent;

handle.style.display = "none";

// nostr profile link
if (handleContainer) {
gui.prepend(
handleContainer,
Expand Down Expand Up @@ -205,66 +207,51 @@ async function twitterProfilePage() {
},
});

// TODO: handle errors: how?

const userFollowsCallback = ({ followSet, latestEvent }) => {
const userFollowsAccount = followSet.has(accountPubkey);

// TODO: put the icons in the twitter buttons
const followIcon = wrapInputTooltip({
input: html.span({
text: userFollowsAccount ? "👤➡️" : "➕👤",
classList: "n-follow-icon",
style: [["cursor", "pointer"]],
onclick: async () => {
log("unfollowing");

let newFollowSet;
let newLatestEvent;

if (userFollowsAccount) {
const newFollowResult = await unfollowAccount({
pubkey: nip07UserPubkey,
currentFollowEvent: latestEvent,
accountPubkey,
relays: nip07Relays,
log,
});

newFollowSet = newFollowResult.followSet;
newLatestEvent = newFollowResult.latestEvent;
} else {
const newFollowResult = await followAccount({
// follows of the user
// the button to follow/unfollow
getFollowSet({
pubkey: nip07UserPubkey,
relays: readRelays,
timeout: 1000 * 60 * 10,
callback: ({ followSet, latestEvent }) => {
gui.gebid("n-tw-follow-unfollow-button")?.remove();

let userFollowsAccount = followSet.has(accountPubkey);

const button = wrapInputTooltip({
id: "n-tw-follow-unfollow-button",
input: createTwitterButton(buttonTobeCloned, accountName, {
emojiIcon: userFollowsAccount ? "➖" : "➕",
modalComponentFn: async () => {
const followParams = {
pubkey: nip07UserPubkey,
currentFollowEvent: latestEvent,
accountPubkey,
accountWriteRelay: writeRelays[0],
relays: nip07Relays,
log,
});

newFollowSet = newFollowResult.followSet;
newLatestEvent = newFollowResult.latestEvent;
}

followIcon.remove();

userFollowsCallback({
followSet: newFollowSet,
latestEvent: newLatestEvent,
});
},
}),
tooltipText: userFollowsAccount
? `You follow ${accountName} on Nostr. Click to unfollow.`
: `You don't follow ${accountName} on Nostr. Click to follow.`,
});
};
};

button.disabled = true;

updateFollowButton(gui.gebid("n-tw-follow-unfollow-button"), "⏳");

try {
if (userFollowsAccount) {
await unfollowAccount(followParams);
} else {
await followAccount(followParams);
}
} catch (err) {
alert("Nostrize error on follow/unfollow: " + err);
}
},
}),
tooltipText: `${userFollowsAccount ? "Unfollow" : "Follow"} ${accountName} on Nostr`,
});

// follows of the user
const userFollowsSubscription = getFollowSet({
pubkey: nip07UserPubkey,
relays: readRelays,
callback: userFollowsCallback,
gui.insertAfter(button, buttonTobeCloned);
},
});

const lnurlData = await getOrInsertCache({
Expand All @@ -275,22 +262,27 @@ async function twitterProfilePage() {
}),
});

createTwitterButton(buttonTobeCloned, accountName, {
const zapButton = wrapInputTooltip({
id: "n-tw-zap-button",
icon: "⚡️",
modalComponentFn: () =>
zapModalComponent({
user: accountName,
metadataEvent,
relays: nip07Relays,
lnurlData,
log,
settings,
}),
input: createTwitterButton(buttonTobeCloned, accountName, {
id: "n-tw-zap-button",
emojiIcon: "⚡️",
modalComponentFn: () =>
zapModalComponent({
user: accountName,
metadataEvent,
relays: nip07Relays,
lnurlData,
log,
settings,
}),
}),
tooltipText: `Zap ${accountName}`,
});

gui.insertAfter(zapButton, buttonTobeCloned);

accountFollowSubscription.close();
userFollowsSubscription.close();
}

twitterProfilePage().catch((e) => console.error(e));
18 changes: 12 additions & 6 deletions src/twitter/profile/twitter-helpers.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import * as gui from "../../imgui-dom/gui.js";

import { setupModal } from "../../components/common.js";

export const updateFollowButton = (button, emojiIcon) => {
button.childNodes[0].childNodes[0].textContent = emojiIcon;
};

export const createTwitterButton = (buttonTobeCloned, accountName, options) => {
const button = buttonTobeCloned.cloneNode(true);

gui.insertAfter(button, buttonTobeCloned);

button.id = options.id;
button.href = "javascript:void(0)";
button.childNodes[0].childNodes[0].remove();
button.childNodes[0].childNodes[0].textContent = options.icon;
button.childNodes[0].childNodes[0].textContent = options.emojiIcon;
button.setAttribute("data-for-account", accountName);

button.addEventListener("mouseenter", () => {
Expand All @@ -22,7 +22,13 @@ export const createTwitterButton = (buttonTobeCloned, accountName, options) => {
});

button.onclick = async () => {
const { modal, closeModal } = await options.modalComponentFn();
const res = await options.modalComponentFn();

if (!res) {
return;
}

const { modal, closeModal } = res;

setupModal(modal, closeModal);
};
Expand Down

0 comments on commit 2327e68

Please sign in to comment.