Skip to content

Commit

Permalink
Comments: Podcastindex-org#233 Remote interaction
Browse files Browse the repository at this point in the history
This commit implements one next step to automate the remote
interactions with comments and commenters.

This uses the WebFinger protocol and API and also the
OStatus Subscribe mechanism.

There are very little guardrails, so some might need to
be implemented later (e.g. verifying the input for the
interactor account).

Known issues:
When the user is first prompted to enter their account,
the opening of a new tab for interaction will be blocked
as a pop-up.

Related issues:
- Podcastindex-org#233
- Podcastindex-org#240

Related links:
-
https://socialhub.activitypub.rocks/t/represent-endpoint-for-remote-interaction/480
  • Loading branch information
dellagustin committed Apr 30, 2023
1 parent 40e8e99 commit 7f59726
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 4 deletions.
20 changes: 20 additions & 0 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,26 @@ function writeThreadcapChunk(processedNodeId, threadcap, sentCommenters, res) {
res.write(JSON.stringify(threadcapChunk) + '\n')
}

// ---------------------------------------------------------
// --------- API to get remote interact url for comments ---
// ---------------------------------------------------------
app.use('/api/comments/remoteInteractUrlPattern', async (req, res) => {
const interactorAccount = req.query.interactorAccount;

console.log('Debug interactorAccount', interactorAccount);

const interactorInstanceHost = interactorAccount.split('@')[1];

const response = await fetch(`https://${interactorInstanceHost}/.well-known/webfinger?` + new URLSearchParams({resource:`acct:${interactorAccount}`}));
const parsedResponse = await response.json();

const linkOStatusSubscribe = parsedResponse.links.find((link) => link.rel === 'http://ostatus.org/schema/1.0/subscribe');

res.send({
remoteInteractUrlPattern: linkOStatusSubscribe.template
})
})

// ------------------------------------------------
// ---------- Static files for API data -----------
// ------------------------------------------------
Expand Down
88 changes: 84 additions & 4 deletions ui/src/components/CommentMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ interface ICommentMenuProps {
}

interface ICommentMenuState {

interactorAccount?: string,
}

class CommentMenu extends React.PureComponent<ICommentMenuProps, ICommentMenuState> {
constructor(props) {
super(props);
this.state = {

interactorAccount: localStorage.getItem('commentsInteractorAccount')
}
}

Expand All @@ -34,13 +34,93 @@ class CommentMenu extends React.PureComponent<ICommentMenuProps, ICommentMenuSta
alert('Link to post copied');
}

async onClickReply(e: React.MouseEvent<HTMLAnchorElement, MouseEvent>): Promise<void> {
e.preventDefault();

const remoteInteractUrlPattern = await this.fetchRemoteInteractUrlPattern();

if(!remoteInteractUrlPattern) {
// TODO: error handling
}

// alert(remoteInteractUrlPattern);
const remoteInteractUrl = remoteInteractUrlPattern.replace('{uri}', encodeURI(this.props.url));
window.open(remoteInteractUrl, '_blank');
}

async onClickFollow(e: React.MouseEvent<HTMLAnchorElement, MouseEvent>): Promise<void> {
e.preventDefault();

const remoteInteractUrlPattern = await this.fetchRemoteInteractUrlPattern();

if(!remoteInteractUrlPattern) {
// TODO: error handling
}

// alert(remoteInteractUrlPattern);
const remoteInteractUrl = remoteInteractUrlPattern.replace('{uri}', encodeURI(CommentMenu.stripLeadingAt(this.props.commenterAccount)));
window.open(remoteInteractUrl, '_blank');
}

async fetchRemoteInteractUrlPattern(): Promise<string> {
let interactorAccount = localStorage.getItem('commentsInteractorAccount');

if(!interactorAccount) {
interactorAccount = prompt('What is your Fediverse account?');

if(interactorAccount) {
localStorage.setItem('commentsInteractorAccount', interactorAccount);
}
}

if(!interactorAccount) {
return;
}

let remoteInteractUrlPattern = localStorage.getItem('commentsRemoteInteractUrlPattern');

if(!remoteInteractUrlPattern) {
const response = await fetch('/api/comments/remoteInteractUrlPattern?' + new URLSearchParams({
interactorAccount: interactorAccount
}));

const parsedResponse = await response.json();

remoteInteractUrlPattern = parsedResponse.remoteInteractUrlPattern;

if(remoteInteractUrlPattern) {
localStorage.setItem('commentsRemoteInteractUrlPattern', remoteInteractUrlPattern);
}
}

return remoteInteractUrlPattern;
}

static stripLeadingAt(account: string) {
return account.substring(1);
}

onClickForgetHomeInstanceHost(e: React.MouseEvent<HTMLAnchorElement, MouseEvent>): void {
e.preventDefault();

localStorage.removeItem('commentsInteractorAccount');
localStorage.removeItem('commentsRemoteInteractUrlPattern');

this.setState({
interactorAccount: undefined
});
}

render(): React.ReactNode {
return (
<menu>
<a href={this.props.url}>Reply to this post</a>
<a href={this.props.url} target='_blank' onClick={(e) => this.onClickReply(e)}>Reply to this post</a>
<a href={this.props.url} onClick={(e) => this.onClickCopyLink(e)}>Copy link to this post</a>
<a href={this.props.url} target='_blank'>Open in original site</a>
<a href={this.props.commenterUrl}>Follow {this.props.commenterAccount}</a>
<a href={this.props.commenterUrl} target='_blank' onClick={(e) => this.onClickFollow(e)}>Follow {this.props.commenterAccount}</a>
{this.state.interactorAccount &&
<a onClick={(e) => this.onClickForgetHomeInstanceHost(e)}>Forget {this.state.interactorAccount}</a>
}
</menu>
)
}
Expand Down

0 comments on commit 7f59726

Please sign in to comment.