Compare commits

...

8 Commits
v0.0.5 ... main

Author SHA1 Message Date
minimo-io
ecfdfe3f7d Better readme for appticles 2024-04-27 17:55:24 -03:00
minimo-io
684e179b19 Corret url of demo! 2024-04-27 17:36:52 -03:00
minimo-io
2a460cb286 Add demo to readme 2024-04-27 17:36:15 -03:00
minimo-io
4be67afbc3 New feature request 2024-04-27 17:29:34 -03:00
minimo-io
3b26739c0a New build 2024-04-27 17:26:15 -03:00
minimo-io
c6024fa644 Removing // @ts-nocheck 2024-04-27 17:19:41 -03:00
minimo-io
8e75b1e4d4 New build 2024-04-27 15:38:34 -03:00
minimo-io
da657383aa Process npubs one by one 2024-04-27 15:33:29 -03:00
10 changed files with 190 additions and 194 deletions

View File

@ -1,10 +1,10 @@
# About appticles
A bunch of mini-apps or appticles created for [minimo.io](https://minimo.io).
A bunch of microapps (aka appticles) created for [minimo.io](https://minimo.io).
These micro-apps were embedded on blog posts for adding extra functionality. They were developed primarily using the Svelte Framework or Alpine.js. Most of them just as proof-of-concepts and for learning purposes.
These microapps are used for adding extra functionality to blog posts and articles. They are develop primarily using the Svelte Framework or Alpine.js.
The site [minimo.io](https://github.com/minimo-io/minimo-11ty) itself was built using [Eleventy](https://github.com/11ty/eleventy).
Minimo.io itself [was built using Eleventy](https://github.com/minimo-io/minimo-11ty).
## Get in touch.

View File

@ -3,22 +3,18 @@
Find out who does and who doesn't follow you back on Nostr.
Let promote some reciprocity here! 😹
> Check out the [demo](https://minimo.io/app/nostr-followback/).
## ToDo
- Actually load builds at minimo.io/app/ or something alike.
- Ask community for help about the bug (see below) so I can keep understanding the protocol and the library.
- Polish the proof-of-concept code, making it TS code and remove `// @ts-nocheck`!
- Create groups to checks to fire simultaneously instad of one by one (to remember: fireing all the `fetchProfile` at once for a given npub resulted in a permanent halt of the process -mainly for big users). Maybe using https://lodash.com/docs `_.chunk(array, [size=1])`
- Create interfaces or new types instead of loose variables
- Save followbackers and not followbackers in lists to see details.
- Create an UI/UX that's worth looking at.
- Close connection with relays after all is checked
- Show the relay list to be used
- Upload a localStorage list of relays
- Upload a localStorage list of relays & older results for folloback
- ~~Actually load builds at minimo.io/app/ or something alike.~~
- ~~Configure Vite for the miniapp to be loaded in the article's url as base.~~
- ~~Remove SvelteKit (just Vite + Svelte).~~
## BUG
- **When user follows lots of people, progress freezes:**<br>
[NDK](https://github.com/nostr-dev-kit/ndk) keeps trying to re-connect to relays, and progress slows down or halts completly. Maybe slow requests down? Are they blocking the requests because they are too many too fast?

File diff suppressed because one or more lines are too long

View File

@ -1,24 +1,24 @@
{
"name": "nostr-followback",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"@tsconfig/svelte": "^5.0.2",
"svelte": "^4.2.12",
"svelte-check": "^3.6.7",
"tslib": "^2.6.2",
"typescript": "^5.2.2",
"vite": "^5.2.0"
},
"dependencies": {
"@nostr-dev-kit/ndk": "^2.8.1"
}
"name": "nostr-followback",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"@tsconfig/svelte": "^5.0.2",
"svelte": "^4.2.12",
"svelte-check": "^3.6.7",
"tslib": "^2.6.2",
"typescript": "^5.2.2",
"vite": "^5.2.0"
},
"dependencies": {
"@nostr-dev-kit/ndk": "^2.8.1"
}
}

View File

@ -1,158 +1,153 @@
<script lang="ts">
// @ts-nocheck
// Import the package
import NDK from "@nostr-dev-kit/ndk";
import { fetchUserProfile } from "./lib/fetchs";
import NDK, { type NDKUserProfile } from "@nostr-dev-kit/ndk";
import type { Querying, FollowListed } from "./lib/types";
import { relays } from "./lib/data/relays";
// let npubToQuery = "npub1wujhdsytm3w6g0mpsqh8v7ezx83jcm64dlkwuqgm5v8lv0pds55ssudkw0";
let npubToQuery = "";
let querying = false;
let userName = "";
let userThumb = "";
let followsCount = 0;
import Form from "./lib/components/form.svelte";
let npubToQuery = "npub1wujhdsytm3w6g0mpsqh8v7ezx83jcm64dlkwuqgm5v8lv0pds55ssudkw0";
let querying: Querying = "uninitiated";
let userProfile: NDKUserProfile | null;
let followsCount = 0;
let followBackCount = 0;
let notFollowBackCount = 0;
let unknownFollowBack = 0;
let totalCountOfContactsChecked = 0;
$: progress = ((totalCountOfContactsChecked / followsCount) * 100).toFixed() | 0;
$: progress = ((totalCountOfContactsChecked / followsCount) * 100) | 0;
$: notFollowBackPercentage = ((notFollowersBack.length / originalFollow.length) * 100) | 0;
let originalFollow = [];
let notFollowersBack = [];
let originalFollow: FollowListed[] = [];
let notFollowersBack: string[] = [];
async function checkFollowBacks() {
try {
const ndk = new NDK({
explicitRelayUrls: relays,
});
await ndk.connect();
querying = "processing";
await ndk.connect(6000);
const user = await fetchUserProfile(npubToQuery, ndk);
// create user instance
const user = ndk.getUser({ npub: npubToQuery });
// query for user profile
userProfile = await user.fetchProfile();
userName = user.profile.name;
userThumb = user.profile.image;
if (userName) {
if (userProfile) {
// console.log(user.profile);
const follows = await user.follows();
const follows = await user.follows({ groupable: false }, false);
// query for a set of user followers
followsCount = follows.size;
let lastFollowerBack;
let i = 0;
follows.forEach(async (follower) => {
//for (const follower of follows) {
// await new Promise((resolve) => setTimeout(resolve, 0));
for await (const follower of follows) {
const followerFollowList = await follower.follows();
originalFollow.push({ npub: follower.npub, followsBack: "-" }); // add to follower list
originalFollow = originalFollow;
try {
originalFollow.push({ npub: follower.npub, followsBack: "-" }); // add to follower list
originalFollow = originalFollow;
if (followerFollowList.size) {
// console.log(followerFollowList);
// check if the user is in the queried user follow list
let doesFollowBack = false;
for (let contact of followerFollowList) {
lastFollowerBack = contact.npub;
if (contact.npub == npubToQuery) {
doesFollowBack = true;
break;
if (followerFollowList.size) {
// console.log(followerFollowList);
// check if the user is in the queried user follow list
let doesFollowBack = false;
for (let contact of followerFollowList) {
lastFollowerBack = contact.npub;
if (contact.npub == npubToQuery) {
doesFollowBack = true;
break;
}
}
}
originalFollow[i].followBack = doesFollowBack ? "1" : "0";
originalFollow[i].followsBack = doesFollowBack ? "1" : "0";
// decision making time
if (doesFollowBack) {
followBackCount++;
totalCountOfContactsChecked++;
// add here the ones who do follow back
// decision making time
if (doesFollowBack) {
followBackCount++;
totalCountOfContactsChecked++;
// add here the ones who do follow back
} else {
notFollowBackCount++;
notFollowersBack.push(follower.npub);
notFollowersBack = notFollowersBack;
totalCountOfContactsChecked++;
}
} else {
notFollowBackCount++;
notFollowersBack.push(follower.npub);
notFollowersBack = notFollowersBack;
unknownFollowBack++;
totalCountOfContactsChecked++;
}
} else {
unknownFollowBack++;
totalCountOfContactsChecked++;
i++;
} catch (error) {
console.log(follower);
console.error(`Error fetching npub:`, error);
}
i++;
});
// follows.forEach(async (follower) => {
// });
}
}
querying = "completed";
} catch (error) {
console.error("Error fetching data:", error);
console.error(`Error fetching data (${npubToQuery}):`, error);
}
}
//checkFollowBacks();
function ItemCount(i: number): string {
let ret = i.toString();
if (i < 10) ret = `0${i}`;
return ret;
}
</script>
<form class="npub-form">
<input disabled={querying && progress < 100} type="text" placeholder="An npub to check" bind:value={npubToQuery} />
<input
type="button"
on:click={async () => {
querying = true;
userName = "";
userThumb = "";
originalFollow = [];
notFollowersBack = [];
followBackCount = 0;
notFollowBackCount = 0;
unknownFollowBack = 0;
totalCountOfContactsChecked = 0;
await checkFollowBacks();
}}
disabled={!npubToQuery && progress < 100}
value="Analyze"
/>
</form>
<Form
{progress}
{querying}
bind:npubToQuery
on:click={async () => {
querying = "uninitiated";
userProfile = null;
originalFollow = [];
notFollowersBack = [];
followBackCount = 0;
notFollowBackCount = 0;
unknownFollowBack = 0;
totalCountOfContactsChecked = 0;
await checkFollowBacks();
}}
/>
<!-- {#if !querying}
{npubToQuery}
{/if} -->
{#if userThumb}
{#if userProfile}
<div class="user-box">
<img src={userThumb} width="50" style="border-radius:100%;" alt="user-thumb" />
<img src={userProfile.image} width="50" style="border-radius:100%;" alt="user-thumb" />
User:
<a href="https://primal.net/p/{npubToQuery}">{userName}</a>
<a href="https://primal.net/p/{npubToQuery}">{userProfile.displayName}</a>
|  Follows: {followsCount}
<br />
Unknown: {unknownFollowBack} | Follow_Back: {followBackCount} |
Unknown: {unknownFollowBack} - Follow_Back: {followBackCount} -
<strong>👉 Not_Follow_Back</strong>
:
<!-- <span title="Actually Counted">{notFollowBackCount}</span>
/ -->
<span title="Actualy counted">{notFollowersBack.length}</span>
<br />
{#if progress < 100}
<p>
Progress =
<strong>{progress}%</strong>
Progress = <strong>{progress}%</strong>
- {totalCountOfContactsChecked} of {followsCount}
<!-- - {totalCountOfContactsChecked} of {followBackCount +
notFollowBackCount +
unknownFollowBack} -->
</p>
{:else}
<p><strong>Completed!</strong></p>
<p><strong>✅ Completed!</strong></p>
{/if}
<hr />
<br />
Results ({originalFollow.length})
<strong>Not followers</strong>
: {notFollowersBack.length} of
{originalFollow.length} ({notFollowBackPercentage}%)
<ul>
{#each originalFollow as item, i}
<li>
#{i + 1} - {@html item.followBack == "0" ? "<span>🔴</span>" : "<span>🟢</span>"}
#{ItemCount(i + 1)} - {@html item.followsBack == "0" ? "<span>🔴</span>" : "<span>🟢</span>"}
<a target="_blank noreferrer noopener" href="https://primal.net/p/{item.npub}">
{item.npub}
</a>
@ -160,30 +155,9 @@
</li>
{/each}
</ul>
<!-- <br />
<strong>They don't follow you ({notFollowersBack.length}):</strong>
<br />
<ul>
{#each notFollowersBack as item, i (item)}
<li>
#{i + 1} - <a href="https://nostr.band/{item}" target="_blank noreferrer noopener">Nostr.Band</a>
/ <a href="https://primal.net/p/{item}" target="_blank noreferrer noopener">Primal</a>
:
{item}
</li>
{/each}
</ul> -->
</div>
{:else if !querying}
{:else if querying == "uninitiated"}
<!-- <p>Let's find out who does not follow you back in Nostr!</p> -->
{:else}
<div class="loader">Loading data...</div>
{/if}
<style>
.npub-form input[type="text"] {
padding: 5px;
width: 50%;
}
</style>

View File

@ -0,0 +1,30 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import type { Querying } from "../types";
const dispatch = createEventDispatcher();
export let npubToQuery = "";
export let progress = 0;
export let querying: Querying;
export let analyzeFn = function () {
dispatch("click");
};
</script>
<form class="npub-form">
<input
type="text"
placeholder="npub to check"
bind:value={npubToQuery}
disabled={querying == "processing" && progress < 100}
/>
<input type="button" on:click={analyzeFn} value="Analyze" />
</form>
<style>
.npub-form input[type="text"] {
padding: 5px;
width: 50%;
}
</style>

View File

@ -9,11 +9,11 @@ export const relays = [
"wss://nostr.mom",
"wss://nostrelay.yeghro.site",
"wss://relay.damus.io",
"wss://relay.nostr.bg",
// "wss://relay.nostr.bg",
"wss://relay.snort.social",
"wss://relay.primal.net",
"wss://nostr.bitcoiner.social",
"wss://nostr.mutinywallet.com",
"wss://relay.current.fyi",
"wss://brb.io",
// "wss://brb.io",
];

View File

@ -1,9 +0,0 @@
export async function fetchUserProfile(npub, ndk) {
const user = ndk.getUser({ npub });
await user.fetchProfile();
return user;
}
export async function fetchNotes(hexkey, ndk) {
const kind1filter = { kinds: [3], authors: [hexkey] };
return ndk.fetchEvent(kind1filter);
}

View File

@ -0,0 +1,6 @@
export type Querying = "completed" | "processing" | "uninitiated";
export type FollowListed = {
npub: string;
followsBack: string;
};

View File

@ -1,20 +1,20 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"resolveJsonModule": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true
},
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
"references": [{ "path": "./tsconfig.node.json" }]
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"resolveJsonModule": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true
},
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
"references": [{ "path": "./tsconfig.node.json" }]
}