From 3441c9bd0e7bcf713b5358a3d316c091af1c459a Mon Sep 17 00:00:00 2001 From: Matthew Exon Date: Sun, 5 Jan 2025 00:23:51 +0100 Subject: [PATCH 1/4] Ratioed: add statistics about reply likes and reply guy score --- ratioed/RatioedPanel.php | 137 ++++++++++++++++++++++++++++++++++ ratioed/templates/help.tpl | 57 +++++++++++++- ratioed/templates/ratioed.tpl | 2 +- 3 files changed, 192 insertions(+), 4 deletions(-) diff --git a/ratioed/RatioedPanel.php b/ratioed/RatioedPanel.php index 19f6dd44..33a009cd 100644 --- a/ratioed/RatioedPanel.php +++ b/ratioed/RatioedPanel.php @@ -8,7 +8,9 @@ use Friendica\Core\Renderer; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\User; +use Friendica\Model\Verb; use Friendica\Module\Moderation\Users\Active; +use Friendica\Protocol\Activity; /** * This class implements the "Behaviour" panel in Moderation/Users @@ -75,6 +77,11 @@ class RatioedPanel extends Active $this->t('Comments last 24h'), $this->t('Reactions last 24h'), $this->t('Ratio last 24h'), + $this->t('Replies last month'), + $this->t('Reply likes'), + $this->t('Respondee likes'), + $this->t('OP likes'), + $this->t('Reply guy score'), ]; $field_names = [ 'name', @@ -87,6 +94,11 @@ class RatioedPanel extends Active 'comments', 'reactions', 'ratio', + 'reply_count', + 'reply_likes', + 'reply_respondee_likes', + 'reply_op_likes', + 'reply_guy_score', ]; $th_users = array_map(null, $header_titles, $valid_orders, $field_names); @@ -125,6 +137,129 @@ class RatioedPanel extends Active ]); } + protected function getReplyGuyRow($contact_uid) + { + $like_vid = Verb::getID(Activity::LIKE); + $post_vid = Verb::getID(Activity::POST); + + /* + * This is a complicated query. + * + * The innermost select retrieves a chain of four posts: an + * original post, a target comment (possibly deep down in the + * thread), a reply from our user, and a like for that reply. + * If there's no like, we still want to count the reply, so we + * use an outer join. + * + * The second select adds "points" for different kinds of + * likes. The outermost select then counts up these points, + * and the number of distinct replies. + */ + $reply_guy_result = DBA::p(' +SELECT + COUNT(distinct reply_id) AS replies_total, + SUM(like_point) AS like_total, + SUM(target_like_point) AS target_like_total, + SUM(original_like_point) AS original_like_total +FROM ( + SELECT + reply_id, + like_date, + like_date IS NOT NULL AS like_point, + like_author = target_author AS target_like_point, + like_author = original_author AS original_like_point + FROM ( + SELECT + original_post.`uri-id` AS original_id, + original_post.`author-id` AS original_author, + original_post.created AS original_date, + target_post.`uri-id` AS target_id, + target_post.`author-id` AS target_author, + target_post.created AS target_date, + reply_post.`uri-id` AS reply_id, + reply_post.`author-id` AS reply_author, + reply_post.created AS reply_date, + like_post.`uri-id` AS like_id, + like_post.`author-id` AS like_author, + like_post.created AS like_date + FROM + post AS original_post + JOIN + post AS target_post + ON + original_post.`uri-id` = target_post.`parent-uri-id` + JOIN + post AS reply_post + ON + target_post.`uri-id` = reply_post.`thr-parent-id` AND + reply_post.`author-id` = ? AND + reply_post.`author-id` != target_post.`author-id` AND + reply_post.`author-id` != original_post.`author-id` AND + reply_post.`uri-id` != reply_post.`thr-parent-id` AND + reply_post.vid = ? AND + reply_post.created > CURDATE() - INTERVAL 1 MONTH + LEFT OUTER JOIN + post AS like_post + ON + reply_post.`uri-id` = like_post.`thr-parent-id` AND + like_post.vid = ? AND + like_post.`author-id` != reply_post.`author-id` + ) AS post_meta +) AS reply_counts +', $contact_uid, $post_vid, $like_vid); + return $reply_guy_result; + } + + // https://stackoverflow.com/a/48283297/235936 + protected function sigFig($value, $digits) + { + if ($value == 0) { + $decimalPlaces = $digits - 1; + } elseif ($value < 0) { + $decimalPlaces = $digits - floor(log10($value * -1)) - 1; + } else { + $decimalPlaces = $digits - floor(log10($value)) - 1; + } + + $answer = ($decimalPlaces > 0) ? + number_format($value, $decimalPlaces) : round($value, $decimalPlaces); + return $answer; + } + + protected function fillReplyGuyData(&$user) { + $reply_guy_result = $this->getReplyGuyRow($user['user_contact_uid']); + if (DBA::isResult($reply_guy_result)) { + $reply_guy_result_row = DBA::fetch($reply_guy_result); + $user['reply_count'] = $reply_guy_result_row['replies_total'] ?? 0; + $user['reply_likes'] = $reply_guy_result_row['like_total'] ?? 0; + $user['reply_respondee_likes'] = $reply_guy_result_row['target_like_total'] ?? 0; + $user['reply_op_likes'] = $reply_guy_result_row['original_like_total'] ?? 0; + + $denominator = $user['reply_likes'] + $user['reply_respondee_likes'] + $user['reply_op_likes']; + if ($user['reply_count'] == 0) { + $user['reply_guy'] = false; + $user['reply_guy_score'] = 0; + } + elseif ($denominator == 0) { + $user['reply_guy'] = true; + $user['reply_guy_score'] = '∞'; + } + else { + $reply_guy_score = $user['reply_count'] / $denominator; + $user['reply_guy'] = $reply_guy_score >= 1.0; + $user['reply_guy_score'] = $this->sigFig($reply_guy_score, 2); + } + } + else { + $user['reply_count'] = "error"; + $user['reply_likes'] = "error"; + $user['reply_respondee_likes'] = "error"; + $user['reply_op_likes'] = "error"; + $user['reply_guy'] = false; + $user['reply_guy_score'] = 0; + } + } + protected function setupUserCallback(): \Closure { Logger::debug("ratioed: setupUserCallback"); @@ -179,6 +314,8 @@ class RatioedPanel extends Active $user['ratioed'] = false; } + $this->fillReplyGuyData($user); + $user = $parentCallback($user); Logger::debug("ratioed: setupUserCallback", [ 'uid' => $user['uid'], diff --git a/ratioed/templates/help.tpl b/ratioed/templates/help.tpl index fee47e34..db6480cf 100644 --- a/ratioed/templates/help.tpl +++ b/ratioed/templates/help.tpl @@ -2,7 +2,7 @@

Ratioed Plugin Help

- This plugin provides administrators with additional statistics about + This plugin provides moderators with additional statistics about the behaviour of users. These may be useful as early warning signs that warrant more carefully watching the behaviour of a user. They are not suitable as a trigger for instantly blocking, @@ -28,7 +28,7 @@

This plugin allows viewing of an actual ratio, calculated over the last 24 hours. This is a useful timeframe for sudden dogpiling - events that administrators might not otherwise notice. The plugin + events that moderators might not otherwise notice. The plugin also calculates other statistics.

Explanation of Statistics

@@ -68,10 +68,61 @@ 24h". It is intended to approximate the traditional ratio as understood on Twitter.

+

Replies last month

+

+ This is the number of times the user posted a reply to someone any time in the last month. +

+

Reply likes

+

+ This is the number of likes received by the user on their + replies to other people's posts in the last month. Replies that + receive likes can be assumed to be more of a valuable + contribution than replies that do not. +

+

Respondee likes

+

+ The number of times in the last month the user replied to + someone else's comment and that person then liked the reply. + Likes to replies are not necessarily a positive thing, but if + the person you're replying to approves the reply, that's a very + good sign. Of course it's also common in a debate for neither + side to like the other side's comments, but the debate still can + be valuable. +

+

OP likes

+

+ The number of times in the last month the user replied on a + thread and the original poster that started the thread liked the + reply. While there is no formal concept of "ownership" of a + thread, conventionally the original poster is assumed to have + started the thread for a reason, and making replies that do not + fulfil that purpose are bad etiquette. Getting approval from + the original poster therefore is a good sign that the user is + posting replies that are wanted. +

+

Reply guy score

+

+ A "reply + guy" is a common Internet phenomenon of people (disproportionately male) + posting unwanted comments on other (disproportionately female) + people's threads, derailing the + conversation. This score loosely approximates this phenomenon, + as the ratio betwen the number of replies and the sum of likes, + respondee likes, and OP likes. This formula gives extra weight + to particularly relevant likes: a reply to a top-level post that + is liked by the original poster scores the maximum of 3 + "points". A score above 1.0 might indicate cause for concern + for moderators. +

+

+ Since this is indicative of long-term behaviour, the score is + calculated over a month instead of 24 hours. +

+

Performance

The statistics are computed from scratch each time the page loads. - It's possible that this might put a heavy load on the database. and + It's possible that this might put a heavy load on the database, and the page may take a long time to load.

Extending

diff --git a/ratioed/templates/ratioed.tpl b/ratioed/templates/ratioed.tpl index 24ae20b8..6ee6b1d0 100644 --- a/ratioed/templates/ratioed.tpl +++ b/ratioed/templates/ratioed.tpl @@ -36,7 +36,7 @@ {{foreach $users as $u}} - + {{$u.name}} From 8ba4cd5f6140a1ad64ad5404e603ed76b8d535f3 Mon Sep 17 00:00:00 2001 From: Matthew Exon Date: Sat, 11 Jan 2025 19:42:03 +0100 Subject: [PATCH 2/4] Ratioed: bump version number --- ratioed/ratioed.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ratioed/ratioed.php b/ratioed/ratioed.php index 581ef89b..443fdaa9 100644 --- a/ratioed/ratioed.php +++ b/ratioed/ratioed.php @@ -2,7 +2,7 @@ /** * Name: Ratioed * Description: Additional moderation user table with statistics about user behaviour - * Version: 0.2 + * Version: 0.3 * Author: Matthew Exon */ From 807598dbd03b9dca3c3ac7d890fe0cb2f8a1bb9f Mon Sep 17 00:00:00 2001 From: Matthew Exon Date: Wed, 15 Jan 2025 19:32:55 +0100 Subject: [PATCH 3/4] Ratioed: wrap long lines --- ratioed/templates/help.tpl | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/ratioed/templates/help.tpl b/ratioed/templates/help.tpl index db6480cf..0d012ca7 100644 --- a/ratioed/templates/help.tpl +++ b/ratioed/templates/help.tpl @@ -70,7 +70,9 @@

Replies last month

- This is the number of times the user posted a reply to someone any time in the last month. + This is the number of times the user posted a reply to someone + else, on a thread the user did not start, any time in the last + month.

Reply likes

@@ -86,8 +88,8 @@ Likes to replies are not necessarily a positive thing, but if the person you're replying to approves the reply, that's a very good sign. Of course it's also common in a debate for neither - side to like the other side's comments, but the debate still can - be valuable. + side to like the other side's comments without that indicating + an unhealthy interaction, so interpret this statistic cautiously.

OP likes

@@ -103,10 +105,10 @@

Reply guy score

A "reply - guy" is a common Internet phenomenon of people (disproportionately male) - posting unwanted comments on other (disproportionately female) - people's threads, derailing the - conversation. This score loosely approximates this phenomenon, + guy" is a common Internet phenomenon of people + (disproportionately male) posting unwanted comments on other + (disproportionately female) people's threads, derailing the + conversation. This score loosely quantifies this phenomenon, as the ratio betwen the number of replies and the sum of likes, respondee likes, and OP likes. This formula gives extra weight to particularly relevant likes: a reply to a top-level post that From ee8c852d38cd74ca770a8d2445c4975607260bfc Mon Sep 17 00:00:00 2001 From: Matthew Exon Date: Wed, 15 Jan 2025 19:33:11 +0100 Subject: [PATCH 4/4] Ratioed: remove action buttons --- ratioed/templates/ratioed.tpl | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/ratioed/templates/ratioed.tpl b/ratioed/templates/ratioed.tpl index 6ee6b1d0..2413ed9d 100644 --- a/ratioed/templates/ratioed.tpl +++ b/ratioed/templates/ratioed.tpl @@ -121,18 +121,7 @@ {{/foreach}} - - {{if $u.is_deletable}} - - - - - - - {{else}} -   - {{/if}} - + {{/foreach}}