Skip to content

Commit

Permalink
Add follow & unfollow functions
Browse files Browse the repository at this point in the history
  • Loading branch information
miladsoft committed Oct 2, 2024
1 parent aedbefe commit dfafd39
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 22 deletions.
53 changes: 37 additions & 16 deletions src/app/components/profile/profile.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ export class ProfileComponent implements OnInit, OnDestroy {
isCurrentUserProfile: Boolean=false;
isFollowing = false;



constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _metadataService: MetadataService,
Expand All @@ -77,18 +75,15 @@ export class ProfileComponent implements OnInit, OnDestroy {

this._route.paramMap.subscribe((params) => {
const routePubKey = params.get('pubkey');
this.routePubKey= routePubKey;
const userPubKey = this._signerService.getPublicKey();

this.isCurrentUserProfile = routePubKey === userPubKey;

const pubKeyToLoad = routePubKey || userPubKey;
this.loadProfile(pubKeyToLoad);

if (!routePubKey) {
this.isCurrentUserProfile = true;
}

this.loadCurrentUserProfile();
this.loadCurrentUserProfile();
});


Expand All @@ -111,7 +106,6 @@ export class ProfileComponent implements OnInit, OnDestroy {
});
}


this._socialService.getFollowersObservable()
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((event) => {
Expand All @@ -137,7 +131,7 @@ export class ProfileComponent implements OnInit, OnDestroy {
this._unsubscribeAll.complete();
}

private async loadProfile(publicKey: string): Promise<void> {
async loadProfile(publicKey: string): Promise<void> {
this.isLoading = true;
this.errorMessage = null;

Expand All @@ -159,8 +153,12 @@ export class ProfileComponent implements OnInit, OnDestroy {
this.metadata = metadata;
this._changeDetectorRef.detectChanges();
}
await this._socialService.getFollowers(publicKey);
await this._socialService.getFollowing(publicKey);

await this._socialService.getFollowers(publicKey);
const currentUserPubKey = this._signerService.getPublicKey();
this.isFollowing = this.followers.includes(currentUserPubKey);

await this._socialService.getFollowing(publicKey);

this._metadataService.getMetadataStream()
.pipe(takeUntil(this._unsubscribeAll))
Expand All @@ -181,8 +179,6 @@ export class ProfileComponent implements OnInit, OnDestroy {
}
}



private async loadCurrentUserProfile(): Promise<void> {
try {
this.currentUserMetadata = null;
Expand Down Expand Up @@ -225,7 +221,32 @@ export class ProfileComponent implements OnInit, OnDestroy {
return this._sanitizer.bypassSecurityTrustUrl(url);
}

toggleFollow() {
this.isFollowing = !this.isFollowing;
}
async toggleFollow(): Promise<void> {
try {
const userPubKey = this._signerService.getPublicKey();
const routePubKey = this.routePubKey || this.userPubKey;

if (!routePubKey || !userPubKey) {
console.error('Public key missing. Unable to toggle follow.');
return;
}

if (this.isFollowing) {
await this._socialService.unfollow(routePubKey);
console.log(`Unfollowed ${routePubKey}`);
} else {
await this._socialService.follow(routePubKey);
console.log(`Followed ${routePubKey}`);
}

this.isFollowing = !this.isFollowing;

this._changeDetectorRef.detectChanges();

} catch (error) {
console.error('Failed to toggle follow:', error);
}
}


}
128 changes: 122 additions & 6 deletions src/app/services/social.service.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { Injectable } from '@angular/core';
import { Filter, NostrEvent } from 'nostr-tools';
import { Filter, NostrEvent, UnsignedEvent, Event } from 'nostr-tools';
import { RelayService } from './relay.service';
import { SignerService } from './signer.service';
import { Subject, Observable } from 'rxjs';

@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class SocialService {

private followersSubject = new Subject<NostrEvent>();
private followingSubject = new Subject<NostrEvent>();

constructor(
private relayService: RelayService
) {}
private relayService: RelayService,
private signerService: SignerService
) { }

getFollowersObservable(): Observable<NostrEvent> {
return this.followersSubject.asObservable();
Expand All @@ -23,6 +24,7 @@ export class SocialService {
return this.followingSubject.asObservable();
}

// Fetch followers
async getFollowers(pubkey: string): Promise<any[]> {
await this.relayService.ensureConnectedRelays();
const pool = this.relayService.getPool();
Expand All @@ -49,6 +51,7 @@ export class SocialService {
});
}

// Fetch who the user is following
async getFollowing(pubkey: string): Promise<any[]> {
await this.relayService.ensureConnectedRelays();
const pool = this.relayService.getPool();
Expand All @@ -66,7 +69,7 @@ export class SocialService {
onevent: (event: NostrEvent) => {
const tags = event.tags.filter((tag) => tag[0] === 'p');
tags.forEach((tag) => {
following.push( tag[1] );
following.push(tag[1]);
this.followingSubject.next(event);
});
},
Expand All @@ -77,4 +80,117 @@ export class SocialService {
});
});
}

// Follow a user
async follow(pubkeyToFollow: string): Promise<void> {
await this.relayService.ensureConnectedRelays();
const pool = this.relayService.getPool();
const currentFollowing = this.getFollowingList();
if (currentFollowing.includes(pubkeyToFollow)) {
console.log(`Already following ${pubkeyToFollow}`);
return;
}

// Add the user to the following list
const newFollowingList = [...currentFollowing, pubkeyToFollow];
this.setFollowingList(newFollowingList);

const unsignedEvent: UnsignedEvent = this.signerService.getUnsignedEvent(3, newFollowingList.map(f => ['p', f]), '');

// Check if using Nostr extension
const isUsingExtension = await this.signerService.isUsingExtension();
let signedEvent: Event;

if (isUsingExtension) {
// Sign using Nostr extension
signedEvent = await this.signerService.signEventWithExtension(unsignedEvent);
} else {
// Sign using private key
const secretKey = await this.signerService.getDecryptedSecretKey();
if (!secretKey) {
throw new Error('Secret key is missing. Unable to follow.');
}
signedEvent = this.signerService.getSignedEvent(unsignedEvent, secretKey);
}

// Publish the signed follow event
this.relayService.publishEventToRelays(signedEvent);
console.log(`Now following ${pubkeyToFollow}`);
}

// Unfollow a user
async unfollow(pubkeyToUnfollow: string): Promise<void> {
await this.relayService.ensureConnectedRelays();
const currentFollowing = this.getFollowingList();
if (!currentFollowing.includes(pubkeyToUnfollow)) {
console.log(`Not following ${pubkeyToUnfollow}`);
return;
}

// Remove the user from the following list
const updatedFollowingList = currentFollowing.filter((pubkey) => pubkey !== pubkeyToUnfollow);
this.setFollowingList(updatedFollowingList);

const unsignedEvent: UnsignedEvent = this.signerService.getUnsignedEvent(3, updatedFollowingList.map(f => ['p', f]), '');

// Check if using Nostr extension
const isUsingExtension = await this.signerService.isUsingExtension();
let signedEvent: Event;

if (isUsingExtension) {
// Sign using Nostr extension
signedEvent = await this.signerService.signEventWithExtension(unsignedEvent);
} else {
// Sign using private key
const secretKey = await this.signerService.getDecryptedSecretKey();
if (!secretKey) {
throw new Error('Secret key is missing. Unable to unfollow.');
}
signedEvent = this.signerService.getSignedEvent(unsignedEvent, secretKey);
}

// Publish the signed unfollow event
this.relayService.publishEventToRelays(signedEvent);
console.log(`Unfollowed ${pubkeyToUnfollow}`);
}

// Retrieve following list as tags for publishing follow/unfollow events
getFollowingListAsTags(): string[][] {
const following = this.getFollowingList();
const tags: string[][] = [];

const relays = this.relayService.getConnectedRelays();

following.forEach((f) => {
relays.forEach((relay) => {
tags.push(['p', f, relay, localStorage.getItem(`${f}`) || '']);
});
});

return tags;
}


setFollowingListFromTags(tags: string[][]): void {
const following: string[] = [];
tags.forEach((t) => {
following.push(t[1]);
});
this.setFollowingList(following);
}

setFollowingList(following: string[]): void {
const followingSet = Array.from(new Set(following));
const newFollowingList = followingSet.filter((s) => s).join(',');
localStorage.setItem('following', newFollowingList);
}

getFollowingList(): string[] {
const followingRaw = localStorage.getItem('following');
if (followingRaw === null || followingRaw === '') {
return [];
}
const following = followingRaw.split(',');
return following.filter((value) => /[a-f0-9]{64}/.test(value));
}
}

0 comments on commit dfafd39

Please sign in to comment.