User:Interiot/Tool/code

#!/usr/bin/perl # License: [[Public domain]] # regression test cases: # Markadet fr.wikipedia.org (11k edits) # Kolja21 de.w

#!/usr/bin/perl 
# License: [[Public domain]]

# regression test cases:
#       Markadet        fr.wikipedia.org        (11k edits)
#       Kolja21         de.wikipedia.org        (5.1k edits)
#       OldakQuill      en.wikipedia.org        (12k edits)
#       Mxn             meta.wikimedia.org      (1.7k edits)
#       Helios89        it.wikipedia.org        (7k edits)

# TODO:
#   - regarding the "403 access denied" problem, contact [email protected] or #wikimedia-tech on freenode
#           - ahh, they actively block screen-scrapers
#           - sweet-talk Hashar or Dom into unblocking, temporarily disable the tool or enable some form of rate limiting, etc.
#   - add a starting-cutoff-date, so renominations for RfA could only include the most recent items
#           - add a # edits per day
#   - use something like this to retrieve the list of namespaces in real-time:
#           http://en.wikiquote.org/wiki/Special:Export/Main_Page
#   - make "minor" actually work well for editcountitis:
#           - eg. for each namespace, present it like:          Category Talk: 23 (13)
#             where "23" is the non-minor edits, and "13" is the minor edits
#   - get it to work with other mediawikis (example: http://brandt-watch.org/bwwiki/Main_Page)
#   - include a date at the end of the top-15 breakdown
#   - change the <div>s to <td>s on graph bars
#   - don't count comments as having an edit summary when it's purely an autocomment
#   - fix the issue where there's an "extra" first result when $offset > 0 
#
#   - REWRITE IN AJAX so we don't have to worry about it being a temporary solution or not
#   - fix the sorting order on the output
#
#   - ??        http://tools.wikimedia.de/~avar/cgi-bin/count


# Possible other analysis graphs:
#   - monthly breakdowns
#           : have all the monthly breakdowns appear in one space on the page, but allow the user to
#             select between them with Javascript
#       - monthly breakdown of major/minor edits (like current red/green...  make major edits on left, with minor edits trailing on right)
#       - monthly breakdown of the number of edits with summaries of /^(rv|revert)/
#       - monthly breakdown, one each for the separate namespaces
#       - on monthly breakdowns, extrapolate the current month forward
#   - allow the user to hit ''(more)'' at the bottom of the namespace breakdowns, allowing them to
#           see a more complete list of top-15
#   - allow the user to restrict the metrics to some specific recent period...  eg. this is
#           something that's sometimes discussed on RfA
#   - any content-based analyses?  (I suppose one would have to know which SQL thingies are quicker than others)

# semi-far-out:
#   - allow the user to see JUST their edits from a specific page, when they click on that page on
#       the top-15 breakdown (furthermore, if structured right, it might let anybody's tool basically to
#       pop up the results of a $user && $page query)
#   - allow the results to be the combination of multiple users (either logged-in-user + anon-IP,
#       and multiple logged-in-users from multiple sites, eg. meta)

use strict;
use warnings;


use CGI;
#use CGI::Carp qw(fatalsToBrowser);
use Date::Parse;
use LWP::Simple;
use HTML::Entities;
use Data::Dumper;

sub LOGFILE {"/home/interiot/public_html/tmp/wannabe_kate.log"}

if ($ENV{QUERY_STRING} eq "code") {     # send ourself when requested
    open FIN, $0 and print "Content-type: text/plain\n\n", <FIN>;
    exit;
}

# fill out using these documents:
#       http://meta.wikimedia.org/wiki/MediaWiki_localisation#Getting_latest_file
#       http://sourceforge.net/docs/E04/
        sub nmspc {
            my @a = map {s/#.*//; s/^\s+|\s+$//g; $_} grep /\S/, split /[\n\r]+/, shift;
            return { "\x00order" => [@a], map { $_,1} @a};
        }

my %valid_namespaces = (
    'en.wikipedia.org' => nmspc(qq[
        Talk:
        Category talk:
        Category:
        Help:
        Help talk:
        Image:
        Image talk:
        MediaWiki:
        MediaWiki talk:
        Portal:
        Portal talk:
        Template:
        Template talk:
        User:
        User talk:
        Wikipedia:
        Wikipedia talk:
    ]),
    'de.wikipedia.org' => nmspc(qq[
        Diskussion:                     # Talk
        Kategorie:                      # Category:
        Kategorie Diskussion:           # Category Talk:
        Hilfe:                          # Help:
        Hilfe Diskussion:               # Help Talk:
        Bild:                           # Image:
        Bild Diskussion:                # Image Talk:
        MediaWiki:                      # MediaWiki:
        MediaWiki Diskussion:           # MediaWiki Talk:
        Portal:                         # Portal:
        Portal Diskussion:              # Portal Talk:
        Vorlage:                        # Template:
        Vorlage Diskussion:             # Template Talk:
        Benutzer:                       # User:
        Benutzer Diskussion:            # User Talk:
        Wikipedia:                      # Wikipedia:
        Wikipedia Diskussion:           # Wikipedia Talk:
    ]),
    'it.wikipedia.org' => nmspc(qq[
        Discussione                     # Talk:
        Categoria                       # Category:
        Discussioni categoria           # Category Talk:
        Aiuto                           # Help:
        Discussioni aiuto               # Help Talk:
        Immagine                        # Image:
        Discussioni immagine            # Image Talk:
        MediaWiki                       # MediaWiki:
        Discussioni MediaWiki           # MediaWiki Talk:
        Template                        # Template:
        Discussioni template            # Template Talk:
        Utente                          # User:
        Discussioni utente              # User Talk:
        Wikipedia                       # Wikipedia:
        Discussioni Wikipedia           # Wikipedia Talk:
    ]),
);

my $query = new CGI;
my $site = $query->param("site");
my $username = CGI::Util::escape($query->param("username"));
    $username =~ s/[\+\s]/_/g;
my $isvalid = 0;
my $this_namespace;

if ($ENV{QUERY_STRING}) {
    $isvalid = 1;
    $isvalid = 0 unless ($site =~ /^[\w\.]*\.(org|com|net)$/i);
    #$isvalid = 0 unless ($username =~ /^[-\w\._]*$/);
    $isvalid = 0 if (length($username) == 0);
}


# data we generate by parsing the output from Wikipedia
my @urls;
my $bandwidth_down = 0;
my %namespace_totals;
my $xml_lang = "";
my $earliest_perldate;
my $latest_perldate;
my %month_totals;
my %month_editsummary_totals;
my %unique_articles;
my %namespace_unique_articles;
my %article_titles;

print "Content-type: text/html; charset=utf-8\n\n";
#cgi_dumper(\%valid_namespaces);
if (!$isvalid) {
    if ($ENV{QUERY_STRING}) {
        print "<font color=red><b>Invalid value</b></font>.  <a href='http://en.wikipedia.org/wiki/Special:Emailuser/Interiot'>email Interiot</a> if this is incorrect.<p><br><br>\n";
    }

    print <<"EOF";
This is a slow substitute for <a
href="http://en.wikipedia.org/wiki/Wikipedia:Kate%27s_Tool">Kate's Tool</a> when it's unavailable.

<form method=GET style="padding-top:1em">
<table><tr><td>username <td><input maxlength=128 name=username value="" title="username">
       <tr><td>site     <td><input maxlength=128 name=site value="en.wikipedia.org" title="site">
       <tr><td>         <td><input type=submit value="Submit">
</table>
</form>

Notes:
<ul>
<li>Green bars are for edit summaries, red bars are for edits with no summaries
<li>The statistics are real-time (it <a href="http://en.wikipedia.org/wiki/Screen_scraping">scrapes</a> data off of the <tt>Special:Contributions</tt> page while you wait).
<li>It's somewhat slow for edit counts over 5000
<li>It's unable to count deleted edits
<li>It should work with most wikis out there that use <a href="http://en.wikipedia.org/wiki/MediaWiki">MediaWiki</a>, since it doesn't need privileged access to the databases.
<!-- <li>This can't be more than a temporary solution for Wikipedia, as it wastes ~1GB/day of extra bandwidth compared to Kate's -->
<li>Source code is in the <a href="http://en.wikipedia.org/wiki/Public_domain">public domain</a> and available <a href="$ENV{SCRIPT_NAME}?code">here</a>
<li>Warning: <a href="http://www.bbc.co.uk/dna/h2g2/A1091350">metrics are evil</a>
</ul>

For bug reports/comments, see <a href="http://en.wikipedia.org/wiki/User_talk:Interiot">User talk:Interiot</a> or <a href="http://en.wikipedia.org/wiki/Special:Emailuser/Interiot">email him</a>.
EOF
} else {
    $this_namespace = $valid_namespaces{lc $site};
    #cgi_dumper(\$this_namespace); exit;

    $username =~ s/^_+|_$//g;

    #print "$site<br>$username\n";
    $namespace_totals{earliest} = get_5000($site, $username, 0);
    #cgi_dumper(\@urls, \%namespace_totals); exit;
    #cgi_dumper(\%unique_articles);
    $namespace_totals{"number of unique articles"} = scalar(keys %unique_articles);
    $namespace_totals{"avg edits per article"} = sprintf("%5.2f", $namespace_totals{total} / $namespace_totals{"number of unique articles"});

    print $xml_lang, <<'EOF';
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <style>
        td {padding: .1em 1em .1em}

        table.months {padding-top: 2em}
        table.months td.date {font-weight: bold}
        table.months td {font-size: 75%}

        div.red, div.green {
            height:1em;
            float:left;
        }
        div.red {background-color: #f00}
        div.green {background-color: #0f0}

        div.topN {
            float: left;
            min-height: 30em;           /* otherwise, they get ALL jumbled up */
        }
        table.topN {
            float: left;
            border: 1px solid black;
        }
        table.topN th {
            background-color: #000;
            color: #fff;
        }
        table.topN td {
            /* override the above */
            padding: .1em .3em;
        }
    </style>
</head>
<body>
EOF


    print "<ul style='padding-left:10em'><a href='$ENV{SCRIPT_NAME}'><i>Go back</i></a> to see caveats or to check another user.</ul>\n";
    print "<h3>User:$username</h3>\n";
    print "<table>\n";
    foreach my $key (sort  keys %namespace_totals) {
        print "<tr><td>", $key, "<td>", $namespace_totals{$key}, "\n";
    }
    print "</table>\n";

       
    #### output the months stats
    #cgi_dumper(\%month_editsummary_totals);
    my @months = list_months();
    my $max_width = 0;
    $max_width = ($_ > $max_width ? $_ : $max_width) foreach (values %month_totals);
    if ($max_width > 0) {
        print "<table class=months>\n";
        foreach my $month (@months) {
            my $no_summary = $month_totals{$month} - $month_editsummary_totals{$month};

            print "<tr><td class=date>$month   <td>", $month_totals{$month}, "\n";
            #print "<td><div class=red style='width:", int(500 * $month_totals{$month} / $max_width), "px'></div>\n";
            print "<td><div class=green style='width:", int(500 * $month_editsummary_totals{$month} / $max_width), "px'></div>\n";
            print     "<div class=red   style='width:", int(500 * $no_summary / $max_width), "px'></div>\n";
        }
        print "</table>\n";
    }


    #### output the top-15 namespace stats
    my $num_to_present = 15;
    if ($this_namespace) {          # only do it if we're sure about the namespaces
        print "<p><br>\n";
        #print "<ul>NOTE: This section has a tendency to hilight a user's \"youthful indiscretions\".  Please take the dates of the edits into account.</ul>\n";
        foreach my $nmspc ("Mainspace", @{$this_namespace->{"\x00order"}}) {
            next unless %{$namespace_unique_articles{$nmspc}};
            my @articles = sort {$namespace_unique_articles{$nmspc}{$b} <=> $namespace_unique_articles{$nmspc}{$a}}
                            grep { $namespace_unique_articles{$nmspc}{$_} > 1}          # filter out items with only 1 edit
                            keys(%{$namespace_unique_articles{$nmspc}});
            next unless @articles;

            #print "<div class=topN>\n";
            print "<table class=topN><tr><th colspan=2>$nmspc\n";
            my @present = splice(@articles, 0, $num_to_present);
            foreach my $article (@present) {
                my $artname = $article_titles{$article};
                if ($nmspc ne 'Mainspace') {
                    $artname =~ s/^.*?://;
                }
                $artname =~ s/\s/&nbsp;/g;
                my $url = "http://$site/w/index.php?title=$article&action=history";
                print "<tr><td>", $namespace_unique_articles{$nmspc}{$article}, "<td><a href='$url'>$artname</a>\n";
            }

            # fill it out so float:left doesn't jumble up
            foreach (@present..14) {
                print "<tr><td>&nbsp;<td>&nbsp;\n";
            }

            print "</table>\n";
            #print "</div>\n";
        }
    }

    #### output the bottom summary
    print "<p style='clear:left'><br><br>If there were any problems, please <a href='http://en.wikipedia.org/wiki/Special:Emailuser/Interiot'>email Interiot</a> or post at <a href='http://en.wikipedia.org/wiki/User_talk:Interiot'>User talk:Interiot</a>.\n";
    #print "<p>Based on these URLs:\n<ul>\n", join("\n", map {"<li><a href='$_>$_</a>"} @urls), "</ul>\n";
    print "<div style='padding:1em 3em; font-size: 60%'>Based directly on these URLs:\n";
    foreach my $ctr (0..$#urls) {
        print "<a href='$urls[$ctr]'>[", ($ctr+1), "]</a>";
        print ", " unless ($ctr >= @urls - 1);
        print "\n";
    }
    print "</div>\n";

 
    #### log the bandwidth used
    open FOUT, ">>" . LOGFILE() or die;
    printf FOUT "%s  %-20s %-30s  %5dK  %7d\n", scalar(localtime), $username, $site,
        int($bandwidth_down / 1024), $namespace_totals{total};
    close FOUT;
}

sub get_5000 {
    my $site = shift;
    my $username = shift;
    my $offset = shift;

    my $earliest = "";

    my $url = "http://$site/w/index.php?title=Special:Contributions&target=$username&offset=${offset}&limit=5000";
    if (! $LWP::Simple::ua) {
        LWP::Simple::_init_ua();
        #$LWP::Simple::ua->agent("Mozilla/4.0 WebTV/2.6 (compatible; MSIE 4.0)");     # apparently they're picky about useragent strings
        $LWP::Simple::ua->agent("Wget/1.9.1");     # apparently they're picky about useragent strings.  Use the same as wget.
    }
    push(@urls, $url);
    if (@urls >= 10) {
        print "Too many pages fetched.  Terminating.<br>\n";
        #cgi_dumper(\@urls); exit;
    }
    my $page;
    if (1) {
        my $request = HTTP::Request->new(GET => $url);
        my $response = $LWP::Simple::ua->request($request);
        if (!$response->is_success) {
            print "While trying to fetch <a href='$url'>$url</a>, $site responded:<br><br>\n", $response->status_line, "<br><br>", $response->content;
            exit;
        }
        $page = $response->content;
        $bandwidth_down += length($page);
        if (0) {
            local *FOUTOUT;
            open FOUTOUT, ">/var/tmp/kate/tmp.out" or die;
            print FOUTOUT $page;
            close FOUTOUT;
        }
    } else {
        open FININ, "</var/tmp/kate/tmp.out" or die;
        local $/ = undef;
        $page = <FININ>;
        close FININ;
    }

    if ($page =~ /(<html [^>]+>)/i) {
        $xml_lang = $1;
    }

    ## parse each individual contribution
    #while ($page =~ /^<li>(\d\d:\d\d,.*)/igm) {
    while ($page =~ /^<li>([^(]+\(<a href="[^"]+action=history.*)/igm) {
        my $this_time;

        local $_ = $1;

        my $edit_summary;
        #$edit_summary++ if (m#<a href="/wiki/[^"]*"\s+title="[^"]*">[^<]*</a>\s*\(#is);
        $edit_summary++ if (/<span class='comment'>/si);

        my $article_url;
        if (m#<a href="/wiki/([^"]+)" title="[^"]+">([^<]+)#si) {
            $article_url = $1;
            $article_titles{$1} = $2;
        }
        $unique_articles{$article_url}++;

        ## strip out all the HTML tags
        s/<[^>]*>//gs;
            
        if (/^(.*?) \(/) {
            my $date = $1;
            $earliest = $date;

            # translate months into english, so Date::Parse chn handle them
            # languages believed to work here:      EN, DE, IT
            $date =~ s/\b(?:gen                 )\b/jan/gix;
            $date =~ s/\b(?:mär                 )\b/mar/gix;
            $date =~ s/\b(?:mai|mag             )\b/may/gix;
            $date =~ s/\b(?:giu                 )\b/jun/gix;
            $date =~ s/\b(?:lug                 )\b/jul/gix;
            $date =~ s/\b(?:ago                 )\b/aug/gix;
            $date =~ s/\b(?:set                 )\b/sep/gix;
            $date =~ s/\b(?:okt|ott             )\b/oct/gix;
            $date =~ s/\b(?:dez|dic             )\b/dec/gix;

            $this_time = str2time($date);
            if ($this_time == 0) {
                #print "XXXXXXXXXXXXXXXXXXXXXXXXX<br>\n";
            } else {
                #print scalar(gmtime($this_time)), "<br>\n";
                $earliest_perldate = $this_time;        # record the earliest and latest month we see
                $latest_perldate ||= $this_time;

                my $monthkey = monthkey(localtime($this_time));
                $month_totals{$monthkey}++;
                $edit_summary && $month_editsummary_totals{$monthkey}++;
            }
        }
        s/^[^()]*\([^()]*\) \([^()]*\) (?:\S )? //;

        my $subspace = "Mainspace";
        if (/^([^\s\d\/:]+(?:\s[^\s\d\/:]+)?:)/) {
            if (!$this_namespace || exists $this_namespace->{$1}) {
                $subspace = $1;
            }
        }
        $namespace_totals{$subspace}++;
        $namespace_totals{total}++;
        $namespace_unique_articles{$subspace}{$article_url}++;

        #print "$_<br>\n";
    }


    ## if they have more than 5000 contributions, go to the next page
    while ($page =~ /href="[^"]+:Contributions[^"]+offset=(\d+)/ig) {
        #print "Trying again at offset $1<br>\n";
        next unless $1 > 0 && ($offset == 0 || $1 < $offset);
        return get_5000($site, $username, $1);      # tail recursion until there are no more
    }

    return $earliest;
}

# returns something like [
#       "2003/10",
#       "2003/11",
#       "2003,12"
# ]
sub list_months {
    my $last_monthkey = '';
    my @ret;
    # yes, this is a fairly odd algorithm.  oh well.
    for (my $date=$earliest_perldate; $date<=$latest_perldate; $date+=10*24*60*60) {
        my $monthkey = monthkey(localtime($date));
        if ($monthkey ne $last_monthkey) {
            push(@ret, $monthkey);
            $last_monthkey = $monthkey;
        }
    }
    return @ret;
}

sub monthkey {($_[5] + 1900) . "/" . ($_[4] + 1)}

sub cgi_dumper {print "<pre>", HTML::Entities::encode(Dumper(@_)), "</pre>"}

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.