Compare commits
2 commits
67089193ea
...
650869e2f7
Author | SHA1 | Date | |
---|---|---|---|
650869e2f7 | |||
2ed5ecd6a1 |
2 changed files with 111 additions and 98 deletions
|
@ -23,13 +23,14 @@ export function fromHtml(html: string, quoteUri?: string | null): string {
|
|||
html.replace(/<br\s?\/?>\r?\n/gi, '\n'),
|
||||
);
|
||||
|
||||
let text = '';
|
||||
// stores if we are parsing any lists.
|
||||
// 0 for a level that is an unordered list, otherwise the counter for the ordered list
|
||||
let listIndex: number[] = [];
|
||||
|
||||
for (const n of dom.childNodes) {
|
||||
analyze(n);
|
||||
}
|
||||
|
||||
return text.trim();
|
||||
return dom.childNodes
|
||||
.map(analyze)
|
||||
.join('')
|
||||
.trim();
|
||||
|
||||
function getText(node: TreeAdapter.Node): string {
|
||||
if (treeAdapter.isTextNode(node)) return node.value;
|
||||
|
@ -43,60 +44,55 @@ export function fromHtml(html: string, quoteUri?: string | null): string {
|
|||
return '';
|
||||
}
|
||||
|
||||
function appendChildren(childNodes: TreeAdapter.ChildNode[]): void {
|
||||
if (childNodes.length > 0) {
|
||||
for (const n of childNodes) {
|
||||
analyze(n);
|
||||
}
|
||||
}
|
||||
function analyzeMultiple(childNodes: TreeAdapter.ChildNode[]): string {
|
||||
return childNodes.map(analyze).join('');
|
||||
}
|
||||
|
||||
function analyze(node: TreeAdapter.Node): void {
|
||||
function analyze(node: TreeAdapter.Node): string {
|
||||
if (treeAdapter.isTextNode(node)) {
|
||||
text += node.value;
|
||||
return;
|
||||
return node.value;
|
||||
}
|
||||
|
||||
// Skip comment or document type node
|
||||
if (!treeAdapter.isElementNode(node)) return;
|
||||
if (!treeAdapter.isElementNode(node)) return '';
|
||||
|
||||
switch (node.nodeName) {
|
||||
case 'br': {
|
||||
text += '\n';
|
||||
break;
|
||||
return '\n';
|
||||
}
|
||||
|
||||
case 'a':
|
||||
{
|
||||
let text = '';
|
||||
// trim spaces away, because some AP servers (app.wafrn.net) send strange
|
||||
// zero width non-break space in strange places and things like that
|
||||
const txt = getText(node).trim();
|
||||
const linkText = getText(node).trim();
|
||||
const href = getAttr(node, 'href');
|
||||
|
||||
// hashtags
|
||||
if (txt.startsWith('#') && href && (attrHas(node, 'rel', 'tag') || attrHas(node, 'class', 'hashtag'))) {
|
||||
text += txt;
|
||||
if (linkText.startsWith('#') && href && (attrHas(node, 'rel', 'tag') || attrHas(node, 'class', 'hashtag'))) {
|
||||
text += linkText;
|
||||
// mentions: a link that starts with `@` and does not include space
|
||||
} else if (txt.startsWith('@') && txt.match(/\s/) == null && !attrHas(node, 'rel', 'me')) {
|
||||
const part = txt.split('@');
|
||||
} else if (linkText.startsWith('@') && linkText.match(/\s/) == null && !attrHas(node, 'rel', 'me')) {
|
||||
const part = linkText.split('@');
|
||||
|
||||
if (part.length === 2 && href) {
|
||||
// restore the host name part
|
||||
const acct = `${txt}@${(new URL(href)).hostname}`;
|
||||
const acct = `${linkText}@${(new URL(href)).hostname}`;
|
||||
text += acct;
|
||||
} else if (part.length === 3) {
|
||||
text += txt;
|
||||
text += linkText;
|
||||
}
|
||||
// other
|
||||
} else {
|
||||
const generateLink = () => {
|
||||
if (!href && !txt) {
|
||||
if (!href && !linkText) {
|
||||
return '';
|
||||
}
|
||||
if (!href) {
|
||||
return txt;
|
||||
return linkText;
|
||||
}
|
||||
if (!txt || txt === href) { // #6383: Missing text node
|
||||
if (!linkText || linkText === href) { // #6383: Missing text node
|
||||
if (href.match(urlRegexFull)) {
|
||||
return href;
|
||||
} else {
|
||||
|
@ -104,78 +100,57 @@ export function fromHtml(html: string, quoteUri?: string | null): string {
|
|||
}
|
||||
}
|
||||
if (href.match(urlRegex) && !href.match(urlRegexFull)) {
|
||||
return `[${txt}](<${href}>)`; // #6846
|
||||
return `[${linkText}](<${href}>)`; // #6846
|
||||
} else {
|
||||
return `[${txt}](${href})`;
|
||||
return `[${linkText}](${href})`;
|
||||
}
|
||||
};
|
||||
|
||||
text += generateLink();
|
||||
}
|
||||
break;
|
||||
return text;
|
||||
}
|
||||
|
||||
case 'h1':
|
||||
{
|
||||
text += '【';
|
||||
appendChildren(node.childNodes);
|
||||
text += '】\n';
|
||||
break;
|
||||
return '【' + analyzeMultiple(node.childNodes) + '】\n';
|
||||
}
|
||||
|
||||
case 'b':
|
||||
case 'strong':
|
||||
{
|
||||
text += '**';
|
||||
appendChildren(node.childNodes);
|
||||
text += '**';
|
||||
break;
|
||||
return '**' + analyzeMultiple(node.childNodes) + '**';
|
||||
}
|
||||
|
||||
case 'small':
|
||||
{
|
||||
text += '<small>';
|
||||
appendChildren(node.childNodes);
|
||||
text += '</small>';
|
||||
break;
|
||||
return '<small>' + analyzeMultiple(node.childNodes) + '</small>';
|
||||
}
|
||||
|
||||
case 's':
|
||||
case 'del':
|
||||
{
|
||||
text += '~~';
|
||||
appendChildren(node.childNodes);
|
||||
text += '~~';
|
||||
break;
|
||||
return '~~' + analyzeMultiple(node.childNodes) + '~~';
|
||||
}
|
||||
|
||||
case 'i':
|
||||
case 'em':
|
||||
{
|
||||
text += '<i>';
|
||||
appendChildren(node.childNodes);
|
||||
text += '</i>';
|
||||
break;
|
||||
return '<i>' + analyzeMultiple(node.childNodes) + '</i>';
|
||||
}
|
||||
|
||||
// block code (<pre><code>)
|
||||
case 'pre': {
|
||||
if (node.childNodes.length === 1 && node.childNodes[0].nodeName === 'code') {
|
||||
text += '\n```\n';
|
||||
text += getText(node.childNodes[0]);
|
||||
text += '\n```\n';
|
||||
return '\n```\n' + getText(node.childNodes[0]) + '\n```\n';
|
||||
} else {
|
||||
appendChildren(node.childNodes);
|
||||
return analyzeMultiple(node.childNodes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// inline code (<code>)
|
||||
case 'code': {
|
||||
text += '`';
|
||||
appendChildren(node.childNodes);
|
||||
text += '`';
|
||||
break;
|
||||
return '`' + analyzeMultiple(node.childNodes) + '`';
|
||||
}
|
||||
|
||||
// inline or block KaTeX
|
||||
|
@ -207,31 +182,27 @@ export function fromHtml(html: string, quoteUri?: string | null): string {
|
|||
// can be rendered as KaTeX, now decide if it is possible to render as inline or not
|
||||
if (/[\r\n]/.test(formula)) {
|
||||
// line break, this must be rendered as a block
|
||||
text += '\n\\[' + formula + '\\]\n';
|
||||
return '\n\\[' + formula + '\\]\n';
|
||||
} else {
|
||||
// render as inline
|
||||
text += '\\(' + formula + '\\)';
|
||||
return '\\(' + formula + '\\)';
|
||||
}
|
||||
} else {
|
||||
// not KaTeX, but if there is a plaintext annotation it can still be rendered as code
|
||||
if (/[\r\n]/.test(formula)) {
|
||||
// line break, this must be rendered as a block
|
||||
text += '\n```\n' + formula + '\n```\n';
|
||||
return '\n```\n' + formula + '\n```\n';
|
||||
} else {
|
||||
// render as inline
|
||||
text += '`' + formula + '`';
|
||||
return '`' + formula + '`';
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'blockquote': {
|
||||
const t = getText(node);
|
||||
if (t) {
|
||||
text += '\n> ';
|
||||
text += t.split('\n').join('\n> ');
|
||||
}
|
||||
break;
|
||||
return analyzeMultiple(node.childNodes)
|
||||
.trim()
|
||||
.replace(/^|\n/g, '\n>');
|
||||
}
|
||||
|
||||
case 'p':
|
||||
|
@ -241,9 +212,50 @@ export function fromHtml(html: string, quoteUri?: string | null): string {
|
|||
case 'h5':
|
||||
case 'h6':
|
||||
{
|
||||
text += '\n\n';
|
||||
appendChildren(node.childNodes);
|
||||
break;
|
||||
return '\n\n' + analyzeMultiple(node.childNodes);
|
||||
}
|
||||
|
||||
// lists and list items
|
||||
case 'ol':
|
||||
case 'ul':
|
||||
{
|
||||
if (node.nodeName == 'ol') {
|
||||
listIndex.push(1);
|
||||
} else {
|
||||
listIndex.push(0);
|
||||
}
|
||||
let text = '\n' + analyzeMultiple(node.childNodes);
|
||||
listIndex.pop();
|
||||
return text;
|
||||
}
|
||||
|
||||
case 'li':
|
||||
{
|
||||
if (listIndex.length == 0) {
|
||||
break;
|
||||
}
|
||||
let text = '\n';
|
||||
|
||||
// pop the current operating on index for manipulation
|
||||
let index = listIndex.pop();
|
||||
// indent the start of the list item respecitve of the level of
|
||||
// nesting of lists
|
||||
//
|
||||
// since we popped the current index, the length will be 0 on
|
||||
// the first level, thus causing no indent on the first level
|
||||
text += ' '.repeat(listIndex.length);
|
||||
if (index == 0) {
|
||||
text += '- ';
|
||||
} else {
|
||||
text += index + ') ';
|
||||
index++;
|
||||
}
|
||||
// done with the index, put it back so nested lists with
|
||||
// analyzeMultiple will work correctly
|
||||
listIndex.push(index);
|
||||
|
||||
text += analyzeMultiple(node.childNodes);
|
||||
return text;
|
||||
}
|
||||
|
||||
// other block elements
|
||||
|
@ -251,30 +263,28 @@ export function fromHtml(html: string, quoteUri?: string | null): string {
|
|||
case 'header':
|
||||
case 'footer':
|
||||
case 'article':
|
||||
case 'li':
|
||||
case 'dt':
|
||||
case 'dd':
|
||||
{
|
||||
text += '\n';
|
||||
appendChildren(node.childNodes);
|
||||
break;
|
||||
return '\n' + analyzeMultiple(node.childNodes);
|
||||
}
|
||||
|
||||
case 'span':
|
||||
{
|
||||
if (attrHas(node, 'class', 'quote-inline') && quoteUri && getText(node).trim() === `RE: ${quoteUri}`) {
|
||||
// embedded quote thingy for backwards compatibility, don't show it
|
||||
return '';
|
||||
} else {
|
||||
appendChildren(node.childNodes);
|
||||
return analyzeMultiple(node.childNodes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: // includes inline elements
|
||||
{
|
||||
appendChildren(node.childNodes);
|
||||
break;
|
||||
return analyzeMultiple(node.childNodes);
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[
|
|||
followerId: follower.id,
|
||||
});
|
||||
|
||||
if (requested) {
|
||||
if (requested > 0) {
|
||||
await FollowRequests.delete({
|
||||
followeeId: followee.id,
|
||||
followerId: follower.id,
|
||||
|
@ -132,7 +132,7 @@ export default async function(_follower: { id: User['id'] }, _followee: { id: Us
|
|||
]);
|
||||
|
||||
// check blocking
|
||||
const [blocking, blocked] = await Promise.all([
|
||||
const [sender_block, receiver_blocks] = await Promise.all([
|
||||
Blockings.findOneBy({
|
||||
blockerId: follower.id,
|
||||
blockeeId: followee.id,
|
||||
|
@ -143,30 +143,33 @@ export default async function(_follower: { id: User['id'] }, _followee: { id: Us
|
|||
}),
|
||||
]);
|
||||
|
||||
if (Users.isRemoteUser(follower) && Users.isLocalUser(followee) && blocked) {
|
||||
// リモートフォローを受けてブロックしていた場合は、エラーにするのではなくRejectを送り返しておしまい。
|
||||
if (Users.isRemoteUser(follower) && Users.isLocalUser(followee) && receiver_blocks) {
|
||||
// remote actor is trying to follow a local user which blocks them
|
||||
// -> follow request is immediately rejected
|
||||
const content = renderActivity(renderReject(renderFollow(follower, followee, requestId), followee));
|
||||
deliver(followee , content, follower.inbox);
|
||||
deliver(followee, content, follower.inbox);
|
||||
return;
|
||||
} else if (Users.isRemoteUser(follower) && Users.isLocalUser(followee) && blocking) {
|
||||
// リモートフォローを受けてブロックされているはずの場合だったら、ブロック解除しておく。
|
||||
await Blockings.delete(blocking.id);
|
||||
} else if (Users.isRemoteUser(follower) && Users.isLocalUser(followee) && sender_block) {
|
||||
// receiving a follow from a remote user that supposedly blocks the followee
|
||||
// this is not possible, so the block must be in error
|
||||
// -> delete the block
|
||||
await Blockings.delete(sender_block.id);
|
||||
} else {
|
||||
// それ以外は単純に例外
|
||||
if (blocking != null) throw new IdentifiableError('710e8fb0-b8c3-4922-be49-d5d93d8e6a6e', 'blocking');
|
||||
if (blocked != null) throw new IdentifiableError('3338392a-f764-498d-8855-db939dcf8c48', 'blocked');
|
||||
// everything else can just be an error on our side
|
||||
if (sender_block != null) throw new IdentifiableError('710e8fb0-b8c3-4922-be49-d5d93d8e6a6e', 'blocking');
|
||||
if (receiver_blocks != null) throw new IdentifiableError('3338392a-f764-498d-8855-db939dcf8c48', 'blocked');
|
||||
}
|
||||
|
||||
const followeeProfile = await UserProfiles.findOneByOrFail({ userId: followee.id });
|
||||
|
||||
// フォロー対象が鍵アカウントである or
|
||||
// フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or
|
||||
// フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである
|
||||
// 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく
|
||||
// The followee is a locked account or
|
||||
// The follower is a bot and the followee is cautios about being followed by bots or
|
||||
// The follower is local and the followee is remote
|
||||
if (followee.isLocked || (followeeProfile.carefulBot && follower.isBot) || (Users.isLocalUser(follower) && Users.isRemoteUser(followee))) {
|
||||
// Can not follow immediately, create a follow request instead.
|
||||
let autoAccept = false;
|
||||
|
||||
// Even for locked accounts, if they are already following, go through immediately.
|
||||
// If the follow already exists, nothing more needs to be done.
|
||||
const following = await Followings.countBy({
|
||||
followerId: follower.id,
|
||||
followeeId: followee.id,
|
||||
|
|
Loading…
Add table
Reference in a new issue