view reproctool.cgi @ 48:9c55ad1214ee

Merged dom's branch
author df
date Wed, 28 Jan 2009 18:30:52 +0000
parents 30bbda81f13b 964393fff57e
children e0dd561faac1
line wrap: on
line source

#!/usr/bin/perl -T
#
# CGI script for assessing reprocessing value of items in EVE-Online based
# on mineral prices.
#
# Released under the revised BSD licence
# Copyright (c) 2009 Dominic Cleal
# Portions copyright (c) 2009 Bjorn Hamels

use warnings;
use strict;

use Switch;
use CGI;
use DBI;
use LWP::UserAgent;
use Storable qw/store_fd fd_retrieve/;

# Settings from params.cgi
our (@dbparams, $img_http_path, $eve_central_url,
     $marks_cache, $marks_cache_expiry);

$img_http_path = '/itemimgs';
$eve_central_url = 'http://eve-central.com/api/evemon';
$marks_cache = 'minerals.cache';
$marks_cache_expiry = 4 * 60 * 60;  # 4 hours

require './params.cgi';

# Begin script
my $cgi = new CGI;
print $cgi->header(-type    => 'text/html',
                   -pragma  => 'no-cache',
                   -expires => '-365d');

sub user_error
{
    my $text = shift;
    print<<END;
<html><head><title>Error</title></head>
<body><font color="#FF0000"><b>Error:</b></font> $text</body></html>
END
    exit;
}

# Inputs
my $cols = $cgi->param('cols') || 4;
my $str_items = $cgi->param('items');

# Generate the first page, where eve-central mineral prices are prefilled.
unless ($str_items)
{
    # Try and pull the marks out of a stored cache
    my $marks;
    if (-e $marks_cache)
    {
        open CACHE, "< $marks_cache" || die("Can't open cache $marks_cache: $!");
        $marks = fd_retrieve(*CACHE) || die("Can't read marks from cache: $!");
        close CACHE;
        
        # Check expiry time
        if (time > ($marks->{timestamp} + $marks_cache_expiry))
        {
            $marks = undef;
        }
    }
    
    unless ($marks)
    {
        # User can enter their own numbers if eve-central was down
        $marks->{$_} = 1 foreach ('Tritanium', 'Pyerite', 'Mexallon',
                                    'Isogen',    'Nocxium', 'Zydrine',
                                    'Megacyte',  'Morphite');
        
        # If cache was unavailable or had expired, pull again from eve-central
        my $ua = LWP::UserAgent->new;
        $ua->agent('reproctool');
        my $resp = $ua->request(HTTP::Request->new(GET => $eve_central_url));
        
        if ($resp->is_success)
        {
            # Store as Mineral => 1234
            $marks = { timestamp => time };
            foreach (split(/[\n\r]/, $resp->content))
            {
                next unless (/<name>(.+)<\/name>.*<price>([0-9\.]+)<\/price>/i);
                $marks->{$1} = $2;
            }
            
            if (-e $marks_cache)
            {
                unlink $marks_cache
                    || die("Unable to unlink cache $marks_cache: $!");
            }
            
            # Store into the cache
            open CACHE, "> $marks_cache"
              || die("Can't open cache $marks_cache to write: $!");
            store_fd($marks, *CACHE) || die("Can't write to cache: $!");
            close CACHE;
        }
    }
    
    delete $marks->{timestamp} if exists $marks->{timestamp};
    
    # What evenutally will be printed.
    print<<END;
<html><head><title>Ramm's RDI's reprocessing tool</title></head>
<body>
Welcome to the sell, reprocess or keep tool. :)
<p />
Here's a <a href="http://rammsrdi.com/img/reproctool.png">screenshot</a>
on how it looks in game.
<p />
Mineral prices below are empire averages from <a
href="http://www.eve-central.com/">EVE-Central</a>. Usually you dont need to
update those yourself, unless the EVE-Central is down, or you live in
special areas and are not welcome in empire space anymore.
<p />
How does this work? Well, you need to copy paste an item list (text) in the
input box below, then press submit. Getting this item list from your
inventory is simple, albeit unusual. Follow these steps:
<ol>
    <li>Put all the items in a container in your station</li>
    <li>Richt click to container and select 'Create contract'.</li>
    <li>Click 'Next' and then again 'Next'.</li>
    <li>A popup will appear which will list the items in the container!
    Richt click the text area of the popup, and select 'Copy'.</li>
    <li>Cancel the contract creation! (We only needed the item list, there
    is no contract needed).</li>
    <li>Paste the text you just copied in the text box below, and click
    'Submit'.</li>
    <li>The page will take some seconds to load, be patient. If the server
    gives you an error, you just copy pasted too many items. Try again with
    half the list.</li>
</ol>
<p />
Tip: Set the number of columns to match your inventory columns for easy comparison.
<p />
<hr>
<form method="post">
    <textarea rows="5" cols="80" name="items"></textarea>
    <table border="0" cellspacing="4">
        <tr>
END
    # Lists the name of the minerals.
    print "<td>$_</td>\n" foreach (keys %{$marks});
    print "</tr><tr>";
    
    foreach (keys %{$marks})
    {
        my $sname = lc substr($_, 0, 4);
        my $fmt = sprintf('%.2f', $marks->{$_});
        print "<td><input type='text' name='$sname' size='7' value='$fmt' /></td>\n";
    }
    
    print <<END;
        </tr>
    </table>
    Columns: <input type='text' name='cols' size='2' value='4' /><br /><br />
    <input type="submit" />
</form>
<br />
Mineral prices are empire averages from <a href="http://www.eve-central.com/">EVE-Central</a>.
</body>
</html>
END

    exit;
}

# Load mineral prices
my $trit = $cgi->param('trit') || user_error('No tritanium price');
my $pyer = $cgi->param('pyer') || user_error('No pyerite price');
my $mexa = $cgi->param('mexa') || user_error('No mexallon price');
my $isog = $cgi->param('isog') || user_error('No isogen price');
my $nocx = $cgi->param('nocx') || user_error('No nocxium price');
my $zydr = $cgi->param('zydr') || user_error('No zydrine price');
my $mega = $cgi->param('mega') || user_error('No megacyte price');
my $morp = $cgi->param('morp') || user_error('No morphite price');

my $db = DBI->connect(@dbparams)
    or die("Database connection failure: $DBI::errstr");

# Strip out line endings
$str_items =~ s/[\n\r]+//g;

# If the items string contains the contract info too, strip it out
$str_items = $1 if ($str_items =~ /The container .+ contains the following items:(.+)/);
$str_items = $1 if ($str_items =~ /(.+)Are you sure you want to continue?/);

my @item_names = split(/\s*,\s*/, $str_items);

# SQL fragment to match all items
my $sql_typenames = '';
$sql_typenames = 'types.typeName = ?' if ($#item_names >= 0);
$sql_typenames .= " OR types.typeName = ?" foreach (1..$#item_names);

# SQL lookup for reprocessing amounts
my $sql_reprocess = <<END;
SELECT
	types.typeID,
	types.typeName,
	groups.groupName,
	attrs.valueInt, -- metaLevel
	types.basePrice,
	graphics.icon,
	SUM(CASE WHEN m1.requiredTypeID = 34 THEN m1.quantity ELSE 0 END), -- [Tritanium]
	SUM(CASE WHEN m1.requiredTypeID = 35 THEN m1.quantity ELSE 0 END), -- [Pyerite]
	SUM(CASE WHEN m1.requiredTypeID = 36 THEN m1.quantity ELSE 0 END), -- [Mexallon]
	SUM(CASE WHEN m1.requiredTypeID = 37 THEN m1.quantity ELSE 0 END), -- [Isogen]
	SUM(CASE WHEN m1.requiredTypeID = 38 THEN m1.quantity ELSE 0 END), -- [Nocxium]
	SUM(CASE WHEN m1.requiredTypeID = 39 THEN m1.quantity ELSE 0 END), -- [Zydrine]
	SUM(CASE WHEN m1.requiredTypeID = 40 THEN m1.quantity ELSE 0 END), -- [Megacyte]
	SUM(CASE WHEN m1.requiredTypeID = 11399 THEN m1.quantity ELSE 0 END) -- [Morphite]
FROM invTypes types
LEFT JOIN dgmTypeAttributes attrs ON types.typeID = attrs.typeID AND attrs.attributeID = 633
INNER JOIN typeActivityMaterials m1 ON types.typeID = m1.typeID
INNER JOIN invGroups groups ON types.groupID = groups.groupID
INNER JOIN eveGraphics graphics ON types.graphicID = graphics.graphicID
WHERE $sql_typenames
GROUP BY types.typeID
ORDER BY typeName ASC
END
my $pre_reprocess = $db->prepare($sql_reprocess);

# Global statistical vars
my ($totalTrit, $totalPyer, $totalMexa, $totalIsog, $totalNocx, $totalZydr,
    $totalMega, $totalMorp, $totalIsk, $meta0, $meta1, $meta2, $meta3, $meta4,
    $meta5, $meta6Plus );

# Execute, bring back one row per item
my ($tid, $tname, $gname, $meta, $basePrice, $icon,
    $ttrit, $tpyer, $tmexa, $tisog, $tnocx, $tzydr, $tmega, $tmorp);

$pre_reprocess->execute(@item_names) or die("Can't lookup items: $DBI::errstr");
$pre_reprocess->bind_columns(undef, \$tid, \$tname, \$gname, \$meta, \$basePrice,
                             \$icon, \$ttrit, \$tpyer, \$tmexa, \$tisog,
                             \$tnocx, \$tzydr, \$tmega, \$tmorp);

my $item_lookup = {};
my @output = ();
while ($pre_reprocess->fetch())
{
    my $item = {};
    my $isk = ($trit * $ttrit) + ($pyer * $tpyer) + ($mexa * $tmexa) +
              ($isog * $tisog) + ($nocx * $tnocx) + ($zydr * $tzydr) +
              ($mega * $tmega) + ($morp * $tmorp);
    my $repDetails = sprintf( "Trit=%d Py=%d Mex=%d Iso=%d Nocx=%d Zyd=%d Mega=%d Morp=%d",
                              $ttrit, $tpyer, $tmexa, $tisog, $tnocx, $tzydr, $tmega, $tmorp );

    # End statistics.
    $totalTrit += $ttrit; $totalPyer += $tpyer; $totalMexa += $tmexa;
    $totalIsog += $tisog; $totalNocx += $tnocx; $totalZydr += $tzydr;
    $totalMega += $tmega; $totalMorp += $tmorp; $totalIsk  += $isk;
    
    $meta = 0 unless defined $meta;
    $item = { id => $tid, name => $tname, meta => $meta, icon => $icon,
              price => $basePrice, reprocessIsk => $isk, reprocessDetails => $repDetails };
    $item_lookup->{$tname} = $item;
}

# Now match up the item lookups to the input list
foreach (sort { lc $a cmp lc $b } @item_names)
{
    push @output, (exists $item_lookup->{$_} ? $item_lookup->{$_}
                                             : { name => $_ } );
}

my $col = 0;
print<<END;
<html><head><title>Ramm's RDI's reprocessing tool</title></head>
<head>
    <style type="text/css">
        .reg
        {
            text-align: center;
        }
    </style>
</head>

<body>
<h1>Results</h1>
Here is how it works, the colour coding:
<ul>
    <li>Items in <font color='#666600'>dark yellow</font> are meta 0.</li>
    <li>Items in grey to white are meta 1, 2 or 3.</li>
    <li>Items in <font color='#4444FF'>blue</font> are meta 4.</li>
    <li>Items in <font color='#FF0000'>red</font> are T2 (meta 5).</li>
    <li>items in <font color='#00FF00'>green</font> are above meta 5.</li>
</ul>
<p />
The number below the item represents the item's value if you would reprocess
and sell the minerals on the market. Basically, if you have the option of
selling that item for a price below that number, its better to keep the item
and reprocess it.
<p />
As you might see, the items are listed similarly to your inventory in EVE.
You will need to sort your inventory window in EVE <b>by name</b> to match.
If you have this browser window next to your inventory window, matching the
columns(!), it is very easy to pick out those valuable items.
<p />
Tip: Because this can be a huge page to load in EVE, resizing might freeze the
game a bit.<br />
Also, clicking on the item gives you the general information of that item. It
is not the same as clicking 'Show info' on the real item. (For example: the
real item might be damaged.)<br />
<a href="http://rammsrdi.com">Ramm's RDI</a>.
<p />
<hr>
<p />
<table>
END

sub pretty_numbers
{
    my $num = shift;
    $num = int($num);
    $num = reverse $num;
    $num =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
    return reverse $num;
}

my $igb = ($ENV{HTTP_USER_AGENT} =~ /EVE-minibrowser/i);

for my $item (@output)
{
    if ($col == $cols)
    {
        print "</tr><tr>\n";
        $col = 0;
    }
    
    my ($img, $alt, $text, $link, $colour);
    
    $alt = $item->{name};
    if (defined $item->{id})
    {
        if ($igb)
        {
            $img = "typeicon:$item->{id}";
            $link = "showinfo:$item->{id}";
        }
        else
        {
            $img = "$img_http_path/icons/icons_items_png/64_64/icon$item->{icon}.png";
        }
        
        switch ($item->{meta})
        {
            case 0      { $colour = "#666600"; $meta0 += 1; }
            case 1      { $colour = "#777777"; $meta1 += 1; }
            case 2      { $colour = "#BBBBBB"; $meta2 += 1; }
            case 3      { $colour = "#FFFFFF"; $meta3 += 1; }
            case 4      { $colour = "#4444FF"; $meta4 += 1; }
            case 5      { $colour = "#FF0000"; $meta5 += 1; }
            case [6..30]{ $colour = "#00FF00"; $meta6Plus += 1; }
        }
        
        if ($item->{meta} >= 4)
        {
            $text = $item->{name};
        }
        else
        {
            $text = pretty_numbers($item->{reprocessIsk});
        }
        
        $alt .= " ($item->{reprocessDetails})";
    }
    else
    {
        $text = 'Unknown item';
        $img = "typeicon:07_15";
    }
    
    print "<td width='64' class='reg'>";
    print "<a href='$link'>" if defined $link;
    print "<img src='$img' width='64' height='64' border='1' ";
    print   "title=\"$alt\" alt=\"$alt\" />";
    print "</a>" if defined $link;
    print "<br />";
    print "<font color='$colour'>" if defined $colour;
    print $text;
    print "</font>" if defined $colour;
    print "</td>\n";
    
    $col++;
}

print<<END;
</tr>
</table>
<hr>
<p />
<h1>Totals</h1>
END
# TODO: df wonders if there is a std::map :)
print "Total ISK baseprice: " . pretty_numbers( $totalIsk ) . "<p />\n";
print "<br />Trit: " . pretty_numbers( $totalTrit ) if ($totalTrit>0);
print "<br />Py: " . pretty_numbers( $totalPyer ) if ($totalPyer>0);
print "<br />Mex: " . pretty_numbers( $totalMexa ) if ($totalMexa>0);
print "<br />Iso: " . pretty_numbers( $totalIsog ) if ($totalIsog>0);
print "<br />Nox: " . pretty_numbers( $totalNocx ) if ($totalNocx>0);
print "<br />Zyd: " . pretty_numbers( $totalZydr ) if ($totalZydr>0);
print "<br />Mega: " . pretty_numbers( $totalMega ) if ($totalMega>0);
print "<br />Morp: " . pretty_numbers( $totalMorp ) if ($totalMorp>0);
print "<br />\n";
print "<br /><font color='#666600'>Meta 0: " . pretty_numbers( $meta0 ) . "</font>" if ($meta0>0);
print "<br /><font color='#777777'>Meta 1: " . pretty_numbers( $meta1 ) . "</font>" if ($meta1>0);
print "<br /><font color='#BBBBBB'>Meta 2: " . pretty_numbers( $meta2 ) . "</font>" if ($meta2>0);
print "<br /><font color='#FFFFFF'>Meta 3: " . pretty_numbers( $meta3 ) . "</font>" if ($meta3>0);
print "<br /><font color='#4444FF'>Meta 4: " . pretty_numbers( $meta4 ) . "</font>" if ($meta4>0);
print "<br /><font color='#FF0000'>Meta 5: " . pretty_numbers( $meta5 ) . " (T2)</font>" if ($meta5>0);
print "<br /><font color='#00FF00'>Meta 6+: " . pretty_numbers( $meta6Plus ) . "</font>" if ($meta6Plus>0);

print<<END;
<hr>
<p />
<h1>Credits</h1>
By an idea of <a href="showinfo:1383//1374652644">hfo ohforf</a>, coded by 
<a href="showinfo:1384//689057791">hfo Omnix</a>. From 
<a href="showinfo:2//1176688893">Ramm's RDI</a>.<br />
How about you donate 0.1% of your total (which got calucalted at 
END
# TODO: Weird things happen when you use the pretty numbers function here :( (replace int)
print int( $totalIsk /1000 );
print<<END
 ISK) to <a href="showinfo:1386//532549592">hfo</a> which will distribute it towards the
creators and corperation of this tool that you are using now :). Thanks!
</body>
</html>
END