Wikipedia:List of Wikipedians by number of edits/Configuration
This report is updated every day.
Source code
edit/*
Copyright 2011 MZMcBride
Copyright 2022 Kunal Mehta <legoktm@debian.org>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use anyhow::Result;
use dbreps2::{str_vec, Frequency, Report};
use log::{debug, info};
use mwbot::{Bot, SaveOptions};
use mysql_async::prelude::*;
use mysql_async::Conn;
use std::collections::HashSet;
use thousands::Separable;
async fn get_user_list(conn: &mut Conn, page: &str) -> Result<HashSet<String>> {
debug!("Getting user list: {}", page);
let rows: Vec<String> = conn
.exec_map(
r#"
/* editcount.rs SLOW_OK */
SELECT DISTINCT
lt_title
FROM page
JOIN pagelinks
ON pl_from = page_id
JOIN linktarget on pl_target_id = lt_id
WHERE page_title = ?
AND page_namespace = 4
AND lt_namespace IN (2,3);
"#,
(page,),
|(lt_title,)| lt_title,
)
.await?;
Ok(rows
.into_iter()
.map(|name| name.replace('_', " "))
.collect())
}
async fn get_bot_list(conn: &mut Conn) -> Result<HashSet<String>> {
debug!("Getting bot list");
let rows: Vec<String> = conn
.query_map(
r#"
/* editcount.rs SLOW_OK */
SELECT
user_name
FROM user
JOIN user_groups
ON user_id = ug_user
WHERE ug_group = 'bot';
"#,
|(user_name,)| user_name,
)
.await?;
Ok(rows
.into_iter()
.map(|name| name.replace('_', " "))
.collect())
}
async fn get_user_groups(conn: &mut Conn, username: &str) -> Result<String> {
debug!("Fetching user groups for {}", username);
let mut rows: Vec<String> = conn
.exec_map(
r#"
/* editcount.rs SLOW_OK */
SELECT
ug_group
FROM user_groups
JOIN user
ON user_id = ug_user
WHERE user_name = ?;
"#,
(username,),
|(ug_group,)| ug_group,
)
.await?;
rows.sort();
let formatted: Vec<_> = rows
.into_iter()
.map(|group| format!("{{{{subst:aug|1={group}}}}}"))
.collect();
Ok(formatted.join(", "))
}
async fn is_active(conn: &mut Conn, username: &str) -> Result<bool> {
debug!("Fetching activity level for {}", username);
let rows: Vec<usize> = conn
.exec_map(
r#"
/* editcount.rs SLOW_OK */
SELECT
1
FROM
recentchanges_userindex
JOIN actor ON rc_actor = actor_id
WHERE
actor_name = ?
LIMIT 1
"#,
(username,),
|(one,)| one,
)
.await?;
Ok(rows.len() == 1)
}
struct UserRow {
user_name: String,
user_editcount: u64,
}
pub struct Row {
name: String,
editcount: u64,
groups: String,
is_active: bool,
}
pub struct EditCount {}
impl Report<Row> for EditCount {
fn title(&self) -> &'static str {
"<placeholder>"
}
fn get_title(&self) -> String {
"Wikipedia:List of Wikipedians by number of edits".to_string()
}
fn frequency(&self) -> Frequency {
Frequency::Daily
}
fn rows_per_page(&self) -> Option<usize> {
Some(1000)
}
fn query(&self) -> &'static str {
r#"
/* editcount.rs SLOW_OK */
SELECT
user_name,
user_editcount
FROM user
WHERE user_editcount > 0
ORDER BY user_editcount DESC
LIMIT 12000;
"#
}
async fn run_query(&self, conn: &mut Conn) -> Result<Vec<Row>> {
let unflagged_bots = get_user_list(
conn,
"List_of_Wikipedians_by_number_of_edits/Unflagged_bots",
)
.await?;
let flagged_bots = get_bot_list(conn).await?;
let opt_out = get_user_list(
conn,
"List_of_Wikipedians_by_number_of_edits/Anonymous",
)
.await?;
let mut processed: usize = 0;
let mut formatted = vec![];
debug!("Starting main query...");
let rows = conn
.query_map(self.query(), |(user_name, user_editcount)| UserRow {
user_name,
user_editcount,
})
.await?;
debug!("Finished main query!");
for row in rows {
if unflagged_bots.contains(&row.user_name)
|| flagged_bots.contains(&row.user_name)
{
continue;
}
let (formatted_name, groups, is_active) =
if opt_out.contains(&row.user_name) {
("[Placeholder]".to_string(), "".to_string(), false)
} else {
let groups = get_user_groups(conn, &row.user_name).await?;
let is_active = is_active(conn, &row.user_name).await?;
(row.user_name, groups, is_active)
};
formatted.push(Row {
name: formatted_name,
editcount: row.user_editcount,
groups,
is_active,
});
processed += 1;
if processed >= 10000 {
break;
}
}
Ok(formatted)
}
fn intro(&self) -> &'static str {
""
}
fn headings(&self) -> Vec<&'static str> {
vec!["User", "Edit count", "User groups"]
}
fn format_row(&self, row: &Row) -> Vec<String> {
str_vec![
if row.is_active {
format!("[[User:{}|{}]]", &row.name, &row.name)
} else {
row.name.to_string()
},
row.editcount.separate_with_commas(),
row.groups
]
}
fn code(&self) -> &'static str {
include_str!("editcount.rs")
}
fn get_intro(&self, index: usize) -> String {
format!(
r#"
=== {} ===
{{| class="wikitable"
|- style="white-space:nowrap;"
! No.
! User
! Edit count
! User groups
"#,
index_to_range(index)
)
}
fn get_footer(&self) -> String {
"|-\n|}\n".to_string()
}
fn title_for_update_check(&self) -> String {
"Wikipedia:List of Wikipedians by number of edits/Age".to_string()
}
async fn post_run(&self, bot: &Bot, debug_mode: bool) -> Result<()> {
info!("Updating Wikipedia:List of Wikipedians by number of edits/Age");
if !debug_mode {
bot.page("Wikipedia:List of Wikipedians by number of edits/Age")?
.save(
"<onlyinclude>~~~~~</onlyinclude>",
&SaveOptions::summary("[[WP:BOT|Bot]]: Updated page."),
)
.await?;
}
Ok(())
}
fn subpage(&self, index: usize) -> String {
format!("{}/{}", self.get_title(), index_to_range(index))
}
fn update_index(&self) -> bool {
false
}
}
fn index_to_range(index: usize) -> String {
let start = ((index - 1) * 1000) + 1;
let end = start + 999;
format!("{start}–{end}")
}
#[test]
fn test_index_to_range() {
assert_eq!(&index_to_range(1), "1–1000");
assert_eq!(&index_to_range(2), "1001–2000");
}