Skip to content
This repository has been archived by the owner on Jan 31, 2024. It is now read-only.

fix: Mastodon API #120

Merged
merged 30 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
82c10de
upd: change deps, fix a few bugs, update converter
Oct 28, 2023
f92773d
fix: lockfile
Oct 28, 2023
43f27a6
upd: simplify importing of mastoconverter, fix bug
Oct 29, 2023
90b666e
fix: await all results on favourites and reblogged
Oct 29, 2023
95bcfd8
fix: followers and following not being able to load
Oct 29, 2023
549bcf7
upd: convertAccount now fetches info from the DB
Oct 29, 2023
d8b0078
fix: emojis not loading on local statuses
Oct 29, 2023
8fd669f
fix: statuses using wrong emojis
Oct 29, 2023
c53323d
upd: add history endpoint, make sure all areas use new convertAccount
Oct 29, 2023
b596a49
upd: add quoteUri resolving and correct reblog/quote handling
Oct 29, 2023
cd1083c
fix: relationships not working on some clients
Oct 29, 2023
8736560
fix: reply edits not staying attached
Oct 29, 2023
b57ec5e
test: avatar and header uploading
Oct 29, 2023
2aa7c1a
upd: remove Bearer from auth when looking up token
Oct 29, 2023
e8e4baf
upd: remove host lookup on file updating
Oct 29, 2023
be7d385
test: update debug lines
Oct 29, 2023
d942d4d
upd: change resolving of _request.files
Oct 29, 2023
c88fbe8
upd: remove debug lines, fix header not being detected
Oct 30, 2023
81def94
upd: allow updating of fields
Oct 30, 2023
e24a574
chore: lint
Oct 30, 2023
46bb5f2
fix: lists not being received properly
Oct 30, 2023
4fa15f2
fix: deleting a list returning an error as success
Oct 30, 2023
20a8bb0
fix: search being broken on dev through mastodon
Oct 30, 2023
d15c588
upd: change handling of suggestions and status trends
Oct 31, 2023
0a9ca19
fix: create trends/tags endpoint
Oct 31, 2023
171ba6f
chore: change style of getStatusTrends
Oct 31, 2023
9838911
fix: suggestions
Oct 31, 2023
c18efe0
upd: add empty return on trends/links
Oct 31, 2023
6673c7f
fix: limit query being returned as string instead of int
Oct 31, 2023
b57b644
chore: remove console log on verify_credentials
Oct 31, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 208 additions & 0 deletions packages/backend/src/core/MfmService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,4 +388,212 @@ export class MfmService {

return `<p>${doc.body.innerHTML}</p>`;
}

@bindThis
public async toMastoHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = [], inline = false, quoteUri: string | null = null) {
if (nodes == null) {
return null;
}

const { window } = new Window();

const doc = window.document;

async function appendChildren(children: mfm.MfmNode[], targetElement: any): Promise<void> {
if (children) {
for (const child of await Promise.all(children.map(async (x) => await (handlers as any)[x.type](x)))) targetElement.appendChild(child);
}
}

const handlers: {
[K in mfm.MfmNode['type']]: (node: mfm.NodeType<K>) => any;
} = {
async bold(node) {
const el = doc.createElement('span');
el.textContent = '**';
await appendChildren(node.children, el);
el.textContent += '**';
return el;
},

async small(node) {
const el = doc.createElement('small');
await appendChildren(node.children, el);
return el;
},

async strike(node) {
const el = doc.createElement('span');
el.textContent = '~~';
await appendChildren(node.children, el);
el.textContent += '~~';
return el;
},

async italic(node) {
const el = doc.createElement('span');
el.textContent = '*';
await appendChildren(node.children, el);
el.textContent += '*';
return el;
},

async fn(node) {
const el = doc.createElement('span');
el.textContent = '*';
await appendChildren(node.children, el);
el.textContent += '*';
return el;
},

blockCode(node) {
const pre = doc.createElement('pre');
const inner = doc.createElement('code');

const nodes = node.props.code
.split(/\r\n|\r|\n/)
.map((x) => doc.createTextNode(x));

for (const x of intersperse<FIXME | 'br'>('br', nodes)) {
inner.appendChild(x === 'br' ? doc.createElement('br') : x);
}

pre.appendChild(inner);
return pre;
},

async center(node) {
const el = doc.createElement('div');
await appendChildren(node.children, el);
return el;
},

emojiCode(node) {
return doc.createTextNode(`\u200B:${node.props.name}:\u200B`);
},

unicodeEmoji(node) {
return doc.createTextNode(node.props.emoji);
},

hashtag: (node) => {
const a = doc.createElement('a');
a.setAttribute('href', `${this.config.url}/tags/${node.props.hashtag}`);
a.textContent = `#${node.props.hashtag}`;
a.setAttribute('rel', 'tag');
a.setAttribute('class', 'hashtag');
return a;
},

inlineCode(node) {
const el = doc.createElement('code');
el.textContent = node.props.code;
return el;
},

mathInline(node) {
const el = doc.createElement('code');
el.textContent = node.props.formula;
return el;
},

mathBlock(node) {
const el = doc.createElement('code');
el.textContent = node.props.formula;
return el;
},

async link(node) {
const a = doc.createElement('a');
a.setAttribute('rel', 'nofollow noopener noreferrer');
a.setAttribute('target', '_blank');
a.setAttribute('href', node.props.url);
await appendChildren(node.children, a);
return a;
},

async mention(node) {
const { username, host, acct } = node.props;
const resolved = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);

const el = doc.createElement('span');
if (!resolved) {
el.textContent = acct;
} else {
el.setAttribute('class', 'h-card');
el.setAttribute('translate', 'no');
const a = doc.createElement('a');
a.setAttribute('href', resolved.url ? resolved.url : resolved.uri);
a.className = 'u-url mention';
const span = doc.createElement('span');
span.textContent = resolved.username || username;
a.textContent = '@';
a.appendChild(span);
el.appendChild(a);
}

return el;
},

async quote(node) {
const el = doc.createElement('blockquote');
await appendChildren(node.children, el);
return el;
},

text(node) {
const el = doc.createElement('span');
const nodes = node.props.text
.split(/\r\n|\r|\n/)
.map((x) => doc.createTextNode(x));

for (const x of intersperse<FIXME | 'br'>('br', nodes)) {
el.appendChild(x === 'br' ? doc.createElement('br') : x);
}

return el;
},

url(node) {
const a = doc.createElement('a');
a.setAttribute('rel', 'nofollow noopener noreferrer');
a.setAttribute('target', '_blank');
a.setAttribute('href', node.props.url);
a.textContent = node.props.url.replace(/^https?:\/\//, '');
return a;
},

search: (node) => {
const a = doc.createElement('a');
a.setAttribute('href', `https"google.com/${node.props.query}`);
a.textContent = node.props.content;
return a;
},

async plain(node) {
const el = doc.createElement('span');
await appendChildren(node.children, el);
return el;
},
};

await appendChildren(nodes, doc.body);

if (quoteUri !== null) {
const a = doc.createElement('a');
a.setAttribute('href', quoteUri);
a.textContent = quoteUri.replace(/^https?:\/\//, '');

const quote = doc.createElement('span');
quote.setAttribute('class', 'quote-inline');
quote.appendChild(doc.createElement('br'));
quote.appendChild(doc.createElement('br'));
quote.innerHTML += 'RE: ';
quote.appendChild(a);

doc.body.appendChild(quote);
}

return inline ? doc.body.innerHTML : `<p>${doc.body.innerHTML}</p>`;
}
}
2 changes: 1 addition & 1 deletion packages/backend/src/core/SearchService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export class SearchService {
}
}
const res = await this.meilisearchNoteIndex!.search(q, {
sort: [`createdAt:${opts.order}`],
sort: [`createdAt:${opts.order ? opts.order : 'desc'}`],
matchingStrategy: 'all',
attributesToRetrieve: ['id', 'createdAt'],
filter: compileQuery(filter),
Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/misc/prelude/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,7 @@ export function toArray<T>(x: T | T[] | undefined): T[] {
export function toSingle<T>(x: T | T[] | undefined): T | undefined {
return Array.isArray(x) ? x[0] : x;
}

export function toSingleLast<T>(x: T | T[] | undefined): T | undefined {
return Array.isArray(x) ? x.at(-1) : x;
}
2 changes: 2 additions & 0 deletions packages/backend/src/server/ServerModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { SigninService } from './api/SigninService.js';
import { SignupApiService } from './api/SignupApiService.js';
import { StreamingApiServerService } from './api/StreamingApiServerService.js';
import { ClientServerService } from './web/ClientServerService.js';
import { MastoConverters } from './api/mastodon/converters.js';
import { FeedService } from './web/FeedService.js';
import { UrlPreviewService } from './web/UrlPreviewService.js';
import { MainChannelService } from './api/stream/channels/main.js';
Expand Down Expand Up @@ -87,6 +88,7 @@ import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
OpenApiServerService,
MastodonApiServerService,
OAuth2ProviderService,
MastoConverters,
],
exports: [
ServerService,
Expand Down
Loading
Loading