/* * Treebird - Lightweight frontend for Pleroma * Copyright (C) 2022 Nekobit * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #define PCRE2_CODE_UNIT_WIDTH 8 #include #include #include #include "reply.h" #include "easprintf.h" #include "../config.h" // Pages #include "../static/post.ctmpl" #define ID_REPLY_SIZE 256 #define ID_RESPONSE "" char* construct_post_box(struct mstdnt_status* reply_status, char* default_content, size_t* size) { #define C_S "checked" #define D_S "disabled" char* reply_html; char id_reply[ID_REPLY_SIZE]; enum mstdnt_visibility_type vis = MSTDNT_VISIBILITY_PUBLIC; // Put hidden post request and check visibility if (reply_status) { snprintf(id_reply, ID_REPLY_SIZE, ID_RESPONSE, reply_status->id); vis = reply_status->visibility; } /* * Mastodont-c orders the visibility type from smallest (PUBLIC) to * largest (LOCAL), so we take advantage of the enum values */ struct post_template tdata = { .prefix = config_url_prefix, .reply_input = reply_status ? id_reply : NULL, .content = default_content, .public_checked = vis == MSTDNT_VISIBILITY_PUBLIC ? C_S : NULL, // You can reply with public to unlisted posts .public_disabled = vis > MSTDNT_VISIBILITY_UNLISTED ? D_S : NULL, .unlisted_checked = vis == MSTDNT_VISIBILITY_UNLISTED ? C_S : NULL, .unlisted_disabled = vis > MSTDNT_VISIBILITY_UNLISTED ? D_S : NULL, .private_checked = vis == MSTDNT_VISIBILITY_PRIVATE ? C_S : NULL, .private_disabled = vis > MSTDNT_VISIBILITY_PRIVATE ? D_S : NULL, .direct_checked = vis == MSTDNT_VISIBILITY_DIRECT ? C_S : NULL, .local_checked = vis == MSTDNT_VISIBILITY_LOCAL ? C_S : NULL, }; return tmpl_gen_post(&tdata, size); } /* Some comments: * - Misskey does not return , but we still regex to make sure it's a highlight * - The order of parameters in a tag can be changed (mastodon does this), * so we just grep for regex href * - Misskey/Mastodon adds an @ symbol in the href param, while pleroma adds /users and honk adds /u */ #define REGEX_REPLY "@(?:)?.*?(?:<\\/span>)?" char* reply_status(struct session* ssn, char* id, struct mstdnt_status* status) { char* content = status->content; size_t content_len = strlen(status->content); char* stat_reply; // Regex pcre2_code* re; PCRE2_SIZE* re_results; pcre2_match_data* re_data; // Regex data int rc; int error; PCRE2_SIZE erroffset; int url_off, url_len, name_off, name_len; // Replies size_t replies_size = 0, replies_size_orig; char* replies = NULL; char* instance_domain = malloc(sizeof(config_instance_url)+sizeof("https:///")+1); // sscanf instead of regex works here and requires less work, we just need to trim off the slash at the end if (sscanf(config_instance_url, "https://%s/", instance_domain) == 0) if (sscanf(config_instance_url, "http://%s/", instance_domain) == 0) { free(instance_domain); return NULL; } instance_domain[strlen(instance_domain)] = '\0'; // Remove ports, if any. Needed for development or if // the server actually supports these char* port_val = strchr(instance_domain, ':'); if (port_val) *port_val = '\0'; // Load first reply if (ssn->logged_in && strcmp(status->account.acct, ssn->acct.acct) != 0) { replies = malloc(replies_size = strlen(status->account.acct)+2); replies[0] = '@'; strcpy(replies+1, status->account.acct); replies[replies_size-1] = ' '; } // Compile regex re = pcre2_compile((PCRE2_SPTR)REGEX_REPLY, PCRE2_ZERO_TERMINATED, 0, &error, &erroffset, NULL); if (re == NULL) { fprintf(stderr, "Couldn't parse regex at offset %ld: %d\n", erroffset, error); free(replies); pcre2_code_free(re); } re_data = pcre2_match_data_create_from_pattern(re, NULL); for (int ind = 0;;) { rc = pcre2_match(re, (PCRE2_SPTR)content, content_len, ind, 0, re_data, NULL); if (rc < 0) break; re_results = pcre2_get_ovector_pointer(re_data); // Store to last result ind = re_results[5]; // Read out url_off = re_results[2]; url_len = re_results[3] - url_off; name_off = re_results[4]; name_len = re_results[5] - name_off; int instance_cmp = strncmp(instance_domain, content+url_off, url_len); // Is this the same as us? // Cut off url_len by one to slice the '/' at the end if (instance_cmp == 0 && strncmp(ssn->acct.acct, content+name_off, name_len) == 0) continue; replies_size_orig = replies_size; replies_size += (instance_cmp!=0?url_len:0)+name_len+3-(instance_cmp==0); // Bool as int :^) // Realloc string replies = realloc(replies, replies_size+1); replies[replies_size_orig] = '@'; memcpy(replies + replies_size_orig + 1, content + name_off, name_len); if (instance_cmp != 0) { replies[replies_size_orig+1+name_len] = '@'; memcpy(replies + replies_size_orig + 1 + name_len + 1, content + url_off, url_len); } replies[replies_size-1] = ' '; } if (replies) replies[replies_size-1] = '\0'; pcre2_match_data_free(re_data); stat_reply = construct_post_box(status, replies, NULL); pcre2_code_free(re); free(replies); free(instance_domain); return stat_reply; }