#! /usr/bin/perl -w require LWP::UserAgent; use strict; #use warnings FATAL => 'all'; use JSON; use POSIX; use Encode; use Carp::Always; use Data::Dumper; use URI::Escape; require Date::Format; my $user = $ARGV[0]; my $tproject = "openSUSE:Factory"; sub fetch_user_infos($) { my ($user) = @_; if (-f "reports/$user") { open( my $fh, '<', "reports/$user" ); my $json_text = <$fh>; my $st = decode_json( $json_text ); close($fh); return ($st->{'mywork'}, $st->{projstat}); } my $ua = LWP::UserAgent->new; $ua->timeout(180); $ua->max_size(100000000); $ua->default_header("Accept" => "application/json"); my $mywork = $ua->get("https://build.opensuse.org/home/requests.json?user=$user"); unless ($mywork->is_success) { die $mywork->status_line; } $mywork = from_json( $mywork->decoded_content, { utf8 => 1 }); my $url = "https://build.opensuse.org/project/status/$tproject?ignore_pending=0"; $url .= "&limit_to_fails=false&limit_to_old=false&include_versions=true&filter_for_user=$user"; my $projstat = $ua->get($url); die $projstat->status_line unless ($projstat->is_success); $projstat = from_json( $projstat->decoded_content, { utf8 => 1 }); my %st = (); $st{'mywork'} = $mywork; $st{'projstat'} = $projstat; # open(my $fh, '>', "reports/$user"); # print STDOUT to_json(\%st); # close $fh; return ($mywork, $projstat); } my $shortener = LWP::UserAgent->new; sub shorten_url($$) { my ($url, $slug) = @_; # return $url; $url = uri_escape($url); my $ret = $shortener->get("http://s.kulow.org/-/?url=$url&api=6cf62823d52e6d95582c07f55acdecc7&custom_url=$slug&overwrite=1"); die $ret->status_line unless ($ret->is_success); return $ret->decoded_content; } my $baseurl = "https://build.opensuse.org"; sub time_distance($) { my ($past) = @_; my $minutes = (time() - $past) / 60; if ($minutes < 1440) { return "less than 1 day"; } if ($minutes < 2520) { return "1 day"; } if ($minutes < 43200) { my $days = floor($minutes / 1440. + 0.5); return "$days days"; } my $months = floor($minutes / 43200. + 0.5); if ($months == 1) { return "1 month"; } else { return "$months months"; } } my %requests_to_ignore; sub explain_request($$) { my ($request, $list) = @_; return if (defined($requests_to_ignore{$request->{id}})); #print Dumper($request); my $actions = $request->{action}; $actions = [$actions] if (ref($actions) eq "HASH"); my $line = ''; for my $action (@{$actions || []}) { my $source = $action->{source}; my $target = $action->{target}; if ($target->{project} eq $tproject) { #print "ignore $request->{id}\n"; next; } my $atype = $action->{type} || ''; if ($atype eq "submit") { $line .= " Submit request from $source->{project}/$source->{package} to $target->{project}\n"; } elsif ($atype eq "delete") { $line .= " Delete request for $target->{project}/$target->{package}\n"; } elsif ($atype eq "maintenance_release") { $line .= " Maintenance release request from $source->{project}/$source->{package} to $target->{project}\n"; } elsif ($atype eq "maintenance_incident") { $line .= " Maintenance incident request from $source->{project} to $target->{project}\n"; } elsif ($atype eq "add_role") { my %person = %{$action->{person}}; $line .= " User $person{name} wants to be $person{role} in $target->{project}\n"; } elsif ($atype eq "change_devel") { $line .= " Package $target->{project}/$target->{package} should be developed in $source->{project}\n"; } else { print STDERR "HUH" . Dumper($action); } } $list->{int($request->{id})} = $line if ($line); } sub generate_report($) { my ($user) = @_; my ($mywork, $projstat) = fetch_user_infos($user); #print to_json($mywork, {pretty => 1 }); #print to_json($projstat, {pretty => 1}); my %projects; for my $package (@{$projstat}) { my $develproject = $package->{develproject} || next; next if ($develproject eq $tproject); $projects{$develproject} ||= []; push($projects{$develproject}, $package); } my %reviews_by; my $report = ''; for my $request (@{$mywork->{review}}) { # as we query the build service as anonymous, we always get the reviews for "the others" my $reviews = $request->{other_open_reviews}; $reviews = [$reviews] if (ref($reviews) eq "HASH"); for my $review (@{$reviews}) { next if ($review->{state} ne 'new'); if (($review->{by_user} || '') eq $user) { $report .= "Request $request->{id} is waiting for your review!\n"; $report .= " https://build.opensuse.org/request/show/$request->{id}\n\n"; $requests_to_ignore{$request->{id}} = 1; next; } # we ignore by_group for now if ($review->{by_group} ) { $requests_to_ignore{$request->{id}} = 1; next; } if ($review->{by_project}) { my $bproj = $review->{by_project}; my $bpack = $review->{by_package}; $reviews_by{"$bproj/$bpack"} = $request; next; } } } for my $project (sort(keys %projects)) { my $lines = {}; for my $package (@{$projects{$project}}) { next if @{$package->{requests_from}}; # do not show version information if there is more important stuff my $showversion = 1; my $ignorechanges = 0; my $key = "$project/$package->{name}"; if ($reviews_by{$key}) { my $r = $reviews_by{$key}; push(@{$lines->{reviews}}, " $package->{name} has request $r->{id} waiting for review!"); delete $reviews_by{$key}; } if ($package->{firstfail} && $package->{develfirstfail}) { my $fail = time_distance($package->{firstfail}); my $comment = $package->{failedcomment} || 'unknown failure'; $comment =~ s,^\s+,,; $comment =~ s,\s+$,,; my $url = "$baseurl/package/live_build_log/"; $url .= uri_escape($tproject) . "/"; $url .= uri_escape($package->{name}) . "/"; $url .= uri_escape($package->{failedrepo}) . "/"; $url .= uri_escape($package->{failedarch}); $url = shorten_url($url, "bf-$package->{name}"); push(@{$lines->{fails}}, " $package->{name} fails for $fail ($comment):"); push(@{$lines->{fails}}, " $url\n"); $ignorechanges = 1; } for my $problem (sort @{$package->{problems}}) { if ($problem eq 'different_changes') { my $url = "$baseurl/package/rdiff/"; $url .= uri_escape($package->{develproject}); $url .= "/" . uri_escape($package->{develpackage}); $url .= "?opackage=" . uri_escape($package->{name}); $url .= "&oproject=" . uri_escape($tproject); if ($ignorechanges == 0) { $url = shorten_url($url, "rd-$package->{name}"); push(@{$lines->{unsubmit}}, " $package->{name} - $url"); $showversion = 0; } } elsif ($problem eq 'currently_declined') { my $url = "https://build.opensuse.org/request/show/$package->{currently_declined}"; push(@{$lines->{declined}}, " $package->{name} - $url"); $showversion = 0; } } for my $request (@{$package->{requests_to}}) { push(@{$lines->{requests}}, " $package->{name} - https://build.opensuse.org/request/show/$request"); $requests_to_ignore{$request} = 1; $showversion = 0; } if ($showversion && $package->{upstream_version}) { push(@{$lines->{upstream}}, " $package->{name} - packaged: $package->{version}, upstream: $package->{upstream_version}"); } } $report .= "Project $project\n" if %$lines; for my $reason (qw(reviews fails declined unsubmit requests upstream)) { next unless $lines->{$reason}; if ($reason eq "fails") { $report .= "\n"; } elsif ($reason eq "upstream") { $report .= "\n Packages with new upstream versions:\n"; } elsif ($reason eq "unsubmit") { $report .= "\n Packages with unsubmitted changes:\n"; } elsif ($reason eq "requests") { $report .= "\n Packages with pending requests:\n"; } elsif ($reason eq "declined") { $report .= "\n Declined submit requests - please check the reason:\n"; } $report .= join("\n",@{$lines->{$reason}}); $report .= "\n" if ($reason ne "fails"); } $report .= "\n" if %$lines; } my %list; %list = (); for my $request (@{$mywork->{declined}}) { explain_request($request, \%list); } if (%list && $report) { $report .= "Your declined requests (not related to your factory packages, please revoke or reopen):\n"; my @lkeys = keys %list; foreach my $request (sort { $a <=> $b } @lkeys) { $report .= $list{$request}; $report .= " https://build.opensuse.org/request/show/$request\n\n"; } } %list = (); for my $request (@{$mywork->{new}}) { explain_request($request, \%list); } if (%list && $report) { $report .= "Other new requests (not related to your factory packages):\n"; my @lkeys = keys %list; foreach my $request (sort { $a <=> $b } @lkeys) { $report .= $list{$request}; $report .= " https://build.opensuse.org/request/show/$request\n\n"; } } return $report; } my $report = generate_report($user); if ($report) { my $url = "$baseurl/stage/project/status?project=" . uri_escape($tproject); $url .= "&limit_to_fails=false&include_versions=true"; $url .= "&filter_for_user=" . uri_escape($user); $url = shorten_url($url, "fs-$user"); my $prefix = < ) { $fortune .= " " . $_; } close(FORTUNE); $report .= "\n\n-- \nYour fortune cookie:\n" . $fortune; use MIME::Lite; use XML::Simple; my $xml = ''; open(USER, "osc meta user -- '$user'|") || die "osc meta user $user failed"; while ( ) { $xml .= $_; } close(USER); my $info = XMLin($xml); my $to = $info->{email}; if (ref($info->{realname}) ne "HASH") { my $name = $info->{realname}; $to = encode('MIME-Header', "$name <$to>"); } my $email = MIME::Lite->new( From => 'Stephan Kulow ', To => $to, Subject => 'Reminder for openSUSE:Factory work', Data => $report, Encoding => '7bit', ); # update from time to time :) $email->add( 'User-Agent' => 'Mozilla/5.0 (X11; Linux x86_64; rv:9.0) Gecko/20111220 Thunderbird/9.0'); $email->add( 'X-Mailer' => 'https://github.com/coolo/factory-auto/blob/master/generate-reminder.pl'); print "From - " . Date::Format::time2str("%a %b %d %T %Y\n", time); $email->print(\*STDOUT); }