package com.jbidwatcher.auction.server.ebay;
import com.jbidwatcher.auction.server.*;
import com.jbidwatcher.auction.*;
import com.jbidwatcher.auction.BadBidException;
import com.jbidwatcher.auction.LoginManager;
import com.jbidwatcher.auction.AuctionServerInterface;
import com.jbidwatcher.util.html.JHTML;
import com.jbidwatcher.util.http.CookieJar;
import com.jbidwatcher.util.http.Http;
import com.jbidwatcher.util.*;
import com.jbidwatcher.util.config.JConfig;
import com.jbidwatcher.util.queue.MQFactory;
//import com.jbidwatcher.my.MyJBidwatcher;
import java.net.URLConnection;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.HashMap;
/**
* Created by IntelliJ IDEA.
* User: mrs
* Date: Feb 26, 2007
* Time: 1:38:12 AM
* To change this template use File | Settings | File Templates.
*/
public class ebayBidder implements com.jbidwatcher.auction.Bidder {
private LoginManager mLogin;
private HashMap<String, Integer> mResultHash = null;
private String mBidResultRegex = null;
private Pattern mFindBidResult;
private TT T;
public ebayBidder(TT countryProperties, LoginManager login) {
T = countryProperties;
mLogin = login;
/**
* Build a simple hashtable of results that bidding might get.
* Not the greatest solution, but it's working okay. A better one
* would be great. -- "you didn't win the item." Post-close message?
*/
if(mResultHash == null) {
mResultHash = new HashMap<String, Integer>();
mResultHash.put("you('re| are) not permitted to bid on their listings.", AuctionServer.BID_ERROR_BANNED); /**/
mResultHash.put("the item is no longer available because the auction has ended.", AuctionServer.BID_ERROR_ENDED);
mResultHash.put("bidding has ended (for|on) this item", AuctionServer.BID_ERROR_ENDED);
mResultHash.put("this listing has ended.", AuctionServer.BID_ERROR_ENDED);
mResultHash.put("cannot proceed", AuctionServer.BID_ERROR_CANNOT);
mResultHash.put("problem with bid amount", AuctionServer.BID_ERROR_AMOUNT);
mResultHash.put("your bid must be at least ", AuctionServer.BID_ERROR_TOO_LOW); /*?*/
mResultHash.put("you('ve| have) been outbid by another bidder", AuctionServer.BID_ERROR_OUTBID);
mResultHash.put("you('ve| have) just been outbid", ebayServer.BID_ERROR_OUTBID);
mResultHash.put("you('re| are) the current high bidder", AuctionServer.BID_WINNING);
mResultHash.put("you('re| are) the high bidder and the reserve price has been met", AuctionServer.BID_WINNING);
mResultHash.put("you('re| are) the first bidder", AuctionServer.BID_WINNING);
mResultHash.put("you('re| are) the high bidder and currently in the lead", AuctionServer.BID_WINNING);
mResultHash.put("you('re| are) currently the highest bidder", AuctionServer.BID_WINNING); /**/
mResultHash.put("you purchased the item", AuctionServer.BID_WINNING);
mResultHash.put("you('re| are) currently the high bidder, but the reserve hasn.t been met", AuctionServer.BID_ERROR_RESERVE_NOT_MET);
mResultHash.put("the reserve price has not been met", AuctionServer.BID_ERROR_RESERVE_NOT_MET);
mResultHash.put("the reserve (price )?has(n.t| not) been met", AuctionServer.BID_ERROR_RESERVE_NOT_MET);
mResultHash.put("your new total must be higher than your current total", AuctionServer.BID_ERROR_TOO_LOW_SELF);
mResultHash.put("this exceeds or is equal to your current bid", AuctionServer.BID_ERROR_TOO_LOW_SELF);
mResultHash.put("you (just )?bought this item", AuctionServer.BID_BOUGHT_ITEM);
mResultHash.put("You('ve| have) purchased more than one of these items", AuctionServer.BID_BOUGHT_ITEM);
mResultHash.put("you committed to buy", AuctionServer.BID_BOUGHT_ITEM);
mResultHash.put("congratulations! you won!", AuctionServer.BID_BOUGHT_ITEM);
mResultHash.put("account suspended", AuctionServer.BID_ERROR_ACCOUNT_SUSPENDED);
mResultHash.put("to enter a higher maximum bid, please enter", AuctionServer.BID_ERROR_TOO_LOW_SELF);
mResultHash.put("you are registered in a country to which the seller doesn.t ship.", AuctionServer.BID_ERROR_WONT_SHIP);
mResultHash.put("this seller has set buyer requirements for this item and only sells to buyers who meet those requirements.", AuctionServer.BID_ERROR_REQUIREMENTS_NOT_MET);
mResultHash.put("sellers are not permitted to purchase their own items", AuctionServer.BID_ERROR_SELLER_CANT_BID);
mResultHash.put("this item is not available for purchase", AuctionServer.BID_NOT_AVAILABLE_FOR_PURCHASE);
}
//"If you want to submit another bid, your new total must be higher than your current total";
StringBuffer superRegex = null;
for(String key : mResultHash.keySet()) {
if (superRegex == null) {
superRegex = new StringBuffer(".*(");
} else {
superRegex.append('|');
}
superRegex.append(key);
}
if(superRegex != null) superRegex.append(").*");
mBidResultRegex = new StringBuilder().append("(?msi)").append(superRegex).toString();
mBidResultRegex = mBidResultRegex.replace(" ", "\\s+");
mFindBidResult = Pattern.compile(mBidResultRegex);
mResultHash.put("sign in", AuctionServer.BID_ERROR_CANT_SIGN_IN);
}
/**
* Get the bidding form (with bid key) from the basic bid page.
*
* @param cj - The cookies for the current session.
* @param inEntry - The auction being bid on.
* @param inCurr - The amount to bid.
* @return - A Form object containing all the input fields from the bid-confirmation page's form.
*
* @throws BadBidException - If there's some kind of an error on the bid confirmation page.
*/
public JHTML.Form getBidForm(CookieJar cj, AuctionEntry inEntry, Currency inCurr) throws BadBidException {
String bidRequest = Externalized.getString("ebayServer.protocol") + T.s("ebayServer.bidHost") + Externalized.getString("ebayServer.V3file");
String bidInfo = getBidInfoURL(inEntry, inCurr);
BidFormReturn rval = null;
try {
String pageName = bidRequest + '?' + bidInfo;
if (JConfig.debugging) inEntry.setLastStatus("Loading bid request...");
rval = getBidFormInternal(pageName, inEntry, cj);
if ( !rval.isSuccess() && (cj = checkSignin(inEntry, rval.getDocument())) != null) {
rval = getBidFormInternal(pageName, inEntry, cj);
}
if( !rval.isSuccess() && (pageName = checkForWarning(inEntry, rval.getDocument())) != null) {
rval = getBidFormInternal(pageName, inEntry, cj);
}
String value = rval.getDocument().grep(".*The following must be corrected before continuing.*");
if(value != null) {
List<String> foo = rval.getDocument().findSequence(".*Enter.*", ".*or more.*");
JConfig.log().dump2File("error-" + ebayServer.BID_ERROR_TOO_LOW + ".html", rval.getBuffer());
throw new BadBidException(foo.get(0) + " " + foo.get(1), ebayServer.BID_ERROR_TOO_LOW);
}
List<String> quickBidError = rval.getDocument().findSequence("error", "Enter.(.*).or more");
if(quickBidError != null) {
JConfig.log().dump2File("error-" + ebayServer.BID_ERROR_TOO_LOW + ".html", rval.getBuffer());
throw new BadBidException(quickBidError.get(1), ebayServer.BID_ERROR_TOO_LOW);
}
// If maxbid wasn't set, log it in the log file so we can debug it later.
String maxbid = rval.getForm().getInputValue("maxbid");
if (maxbid == null || maxbid.length() == 0) {
JConfig.log().logFile("Bad form response to: " + pageName, rval.getBuffer());
}
if (rval.isSuccess()) return rval.getForm();
} catch (IOException e) {
JConfig.log().handleException("Failure to get the bid key! BID FAILURE!", e);
}
// If we never got a valid return value (e.g. an exception was thrown early), punt.
if(rval == null) return null;
if(rval.getDocument() != null) {
checkSignOn(rval.getDocument());
checkBidErrors(rval);
}
// TODO -- Move this to a registered 'alternative parsing' class, which iterates over objects calling recognizeBidPage until getting a positive result, or running out.
// if(JConfig.queryConfiguration("my.jbidwatcher.enabled", "false").equals("true") &&
// JConfig.queryConfiguration("my.jbidwatcher.id") != null) {
// String recognize = MyJBidwatcher.getInstance().recognizeBidpage(inEntry.getIdentifier(), rval.getBuffer());
// Integer remote_result = null;
// try {
// remote_result = Integer.parseInt(recognize);
// } catch(NumberFormatException nfe) {
// // Ignore it for now...
// JConfig.log().logDebug(recognize);
// }
//
// if(remote_result != null && remote_result != AuctionServer.BID_ERROR_UNKNOWN) {
// throw new BadBidException("Remote-checked result", remote_result);
// }
// }
if(JConfig.debugging) inEntry.setLastStatus("Failed to bid. 'Show Last Error' from context menu to see the failure page from the bid attempt.");
JConfig.log().dump2File("unknown-" + inEntry.getIdentifier() + ".html", rval.getBuffer());
inEntry.setErrorPage(rval.getBuffer());
// We don't recognize this error. Damn. Log it and freak.
JConfig.log().logFile(bidInfo, rval.getBuffer());
return null;
}
private String checkForWarning(AuctionEntry inEntry, JHTML htmlDocument) throws UnsupportedEncodingException {
String pageName = null;
if (htmlDocument.grep(T.s("ebayServer.warningPage")) != null) {
JHTML.Form continueForm = htmlDocument.getFormWithInput("firedFilterId");
if (continueForm != null) {
inEntry.setLastStatus("Trying to 'continue' for the actual bid.");
pageName = continueForm.getCGI();
pageName = pageName.replaceFirst("%[A-F][A-F0-9]%A0", "%A0");
}
}
return pageName;
}
// TODO/FIXME -- Under bad circumstances, inEntry.getIdentifier() can return null.
private String getBidInfoURL(AuctionEntry inEntry, Currency inCurr) {
String bidInfo = Externalized.getString("ebayServer.bidCmd") + "&co_partnerid=" + Externalized.getString("ebayServer.itemCGI") + inEntry.getIdentifier() + "&fb=2";
bidInfo += Externalized.getString("ebayServer.bidCGI") + inCurr.getValue();
return bidInfo;
}
private CookieJar checkSignin(AuctionEntry inEntry, JHTML htmlDocument) {
String signOn = htmlDocument.getTitle();
if (signOn != null) {
JConfig.log().logDebug("Checking sign in as bid key load failed!");
if (StringTools.startsWithIgnoreCase(signOn, "sign in")) {
// This means we somehow failed to keep the login in place. Bad news, in the middle of a snipe.
JConfig.log().logDebug("Being prompted again for sign in, retrying.");
if(JConfig.debugging) inEntry.setLastStatus("Not done loading bid request, got re-login request...");
CookieJar cj = mLogin.getSignInCookie(null);
if(JConfig.debugging) inEntry.setLastStatus("Done re-logging in, retrying load bid request.");
return cj;
}
}
return null;
}
private class BidFormReturn {
private boolean mSuccess;
private JHTML.Form mForm;
private JHTML mDocument;
private StringBuffer mBuffer;
public boolean isSuccess() { return mSuccess; }
public JHTML.Form getForm() { return mForm; }
public JHTML getDocument() { return mDocument; }
public StringBuffer getBuffer() { return mBuffer; }
private BidFormReturn(boolean success, JHTML.Form form, JHTML document, StringBuffer buffer) {
mSuccess = success;
mForm = form;
mDocument = document;
mBuffer = buffer;
}
}
private BidFormReturn getBidFormInternal(String pageName, AuctionEntry inEntry, CookieJar cj) throws IOException {
URLConnection huc = cj.connect(pageName);
StringBuffer loadedPage;
// If we failed to load, punt. Treat it as success, but with a null form result.
if (huc == null || (loadedPage = Http.net().receivePage(huc)) == null) {
return new BidFormReturn(true, null, null, null);
}
JHTML htmlDocument = new JHTML(loadedPage);
JHTML.Form bidForm = htmlDocument.getFormWithInput("key");
// eBay's testing a new form without the key...
if(bidForm == null) {
bidForm = htmlDocument.getFormWithInput("maxbid");
}
if (bidForm != null) {
if (JConfig.debugging) inEntry.setLastStatus("Done loading bid request, got form...");
return new BidFormReturn(true, bidForm, htmlDocument, loadedPage);
}
return new BidFormReturn(false, null, htmlDocument, loadedPage);
}
private void checkBidErrors(BidFormReturn inVal) throws BadBidException {
JHTML htmlDocument = inVal.getDocument();
String errMsg = htmlDocument.grep(mBidResultRegex);
if(errMsg != null) {
Matcher bidMatch = mFindBidResult.matcher(errMsg);
bidMatch.find();
String matched_error = bidMatch.group().toLowerCase();
Integer result = getMatchedResult(matched_error);
JConfig.log().dump2File("error-" + result + ".html", inVal.getBuffer());
throw new BadBidException(matched_error, result);
} else {
String amount = htmlDocument.getNextContentAfterRegex("\\s*Enter\\s*");
if (amount != null) {
String orMore = htmlDocument.getNextContent();
if (orMore != null && orMore.indexOf("or more") != -1) {
JConfig.log().dump2File("error-" + ebayServer.BID_ERROR_TOO_LOW + ".html", inVal.getBuffer());
throw new BadBidException("Enter " + amount + orMore, ebayServer.BID_ERROR_TOO_LOW);
}
}
}
}
private void checkSignOn(JHTML htmlDocument) throws BadBidException {
String signOn = htmlDocument.getTitle();
if(signOn != null && signOn.equalsIgnoreCase("Sign In")) throw new BadBidException("sign in", AuctionServerInterface.BID_ERROR_CANT_SIGN_IN);
}
private Integer getMatchedResult(String matched_text) {
for (String regex : mResultHash.keySet()) {
String hacked = "(?msi).*" + regex.replace(" ", "\\s+") + ".*";
if(matched_text.matches(hacked)) return mResultHash.get(regex);
}
return null;
}
public int buy(AuctionEntry ae, int quantity) {
String buyRequest = T.s("ebayServer.buyRequest");
buyRequest += "&item=" + ae.getIdentifier() + "&quantity=" + quantity;
StringBuffer sb;
try {
CookieJar cj = mLogin.getNecessaryCookie(false);
sb = cj.getPage(buyRequest, null, null);
JHTML doBuy = new JHTML(sb);
JHTML.Form buyForm = doBuy.getFormWithInput("uiid");
if (buyForm != null) {
buyForm.delInput("BIN_button");
StringBuffer loadedPage = cj.getPage(buyForm.getAction(), buyForm.getFormData(), buyRequest);
if (loadedPage == null) return AuctionServerInterface.BID_ERROR_CONNECTION;
return handlePostBidBuyPage(cj, loadedPage, buyForm, ae);
}
} catch (CookieJar.CookieException ignored) {
return AuctionServerInterface.BID_ERROR_CONNECTION;
} catch (UnsupportedEncodingException uee) {
JConfig.log().handleException("UTF-8 not supported locally, can't URLEncode buy form.", uee);
return AuctionServerInterface.BID_ERROR_CONNECTION;
}
ae.setErrorPage(sb);
return AuctionServerInterface.BID_ERROR_UNKNOWN;
}
public int bid(AuctionEntry inEntry, Currency inBid, int inQuantity) {
UpdateBlocker.startBlocking();
if(JConfig.queryConfiguration("sound.enable", "false").equals("true")) MQFactory.getConcrete("sfx").enqueue("/audio/bid.mp3");
CookieJar cj = mLogin.getNecessaryCookie(false);
JHTML.Form bidForm;
try {
bidForm = getBidForm(cj, inEntry, inBid);
} catch(BadBidException bbe) {
UpdateBlocker.endBlocking();
return bbe.getResult();
}
if (bidForm != null) {
int rval = placeFinalBid(cj, bidForm, inEntry, inBid, inQuantity);
UpdateBlocker.endBlocking();
return rval;
}
JConfig.log().logMessage("Bad/nonexistent key read in bid, or connection failure!");
UpdateBlocker.endBlocking();
return AuctionServerInterface.BID_ERROR_UNKNOWN;
}
public int placeFinalBid(CookieJar cj, JHTML.Form bidForm, AuctionEntry inEntry, Currency inBid, int inQuantity) {
String bidRequest = Externalized.getString("ebayServer.protocol") + T.s("ebayServer.bidHost") + Externalized.getString("ebayServer.V3file");
String bidInfo = Externalized.getString("ebayServer.bidCmd") + Externalized.getString("ebayServer.itemCGI") + inEntry.getIdentifier() +
Externalized.getString("ebayServer.quantCGI") + inQuantity +
Externalized.getString("ebayServer.bidCGI") + inBid.getValue();
String bidURL = bidRequest + '?' + bidInfo;
bidForm.delInput("BIN_button");
StringBuffer loadedPage = null;
try {
if (JConfig.debugging) inEntry.setLastStatus("Submitting bid form.");
loadedPage = cj.getPage(bidForm.getAction(), bidForm.getFormData(), bidURL);
if (JConfig.debugging) inEntry.setLastStatus("Done submitting bid form.");
} catch (UnsupportedEncodingException uee) {
JConfig.log().handleException("UTF-8 not supported locally, can't URLEncode bid form.", uee);
} catch (CookieJar.CookieException ignored) {
return AuctionServerInterface.BID_ERROR_CONNECTION;
}
if (loadedPage == null) {
return AuctionServerInterface.BID_ERROR_CONNECTION;
}
return handlePostBidBuyPage(cj, loadedPage, bidForm, inEntry);
}
private int handlePostBidBuyPage(CookieJar cj, StringBuffer loadedPage, JHTML.Form bidForm, AuctionEntry inEntry) {
if(JConfig.debugging) inEntry.setLastStatus("Loading post-bid data.");
JHTML htmlDocument = new JHTML(loadedPage);
JHTML.Form continueForm = htmlDocument.getFormWithInput("firedFilterId");
if(continueForm != null) {
try {
inEntry.setLastStatus("Trying to 'continue' to the bid result page.");
String cgi = continueForm.getFormData();
// For some reason, the continue page represents the currency as
// separated from the amount with a '0xA0' character. When encoding,
// this becomes...broken somehow, and adds an extra character, which
// does not work when bidding.
cgi = cgi.replaceFirst("%[A-F][A-F0-9]%A0", "%A0");
URLConnection huc = cj.connect(continueForm.getAction(), cgi, null, true, null);
// We failed to load, entirely. Punt.
if (huc == null) return AuctionServerInterface.BID_ERROR_CONNECTION;
loadedPage = Http.net().receivePage(huc);
// We failed to load. Punt.
if (loadedPage == null) return AuctionServerInterface.BID_ERROR_CONNECTION;
htmlDocument = new JHTML(loadedPage);
} catch(Exception ignored) {
return AuctionServerInterface.BID_ERROR_CONNECTION;
}
}
String errMsg = htmlDocument.grep(mBidResultRegex);
if (errMsg != null) {
Matcher bidMatch = mFindBidResult.matcher(errMsg);
bidMatch.find();
String matched_error = bidMatch.group().toLowerCase();
Integer bidResult = getMatchedResult(matched_error);
JConfig.log().dump2File("error-" + bidResult + ".html", loadedPage);
int result = 0;
if (bidResult != null) {
result = bidResult;
if (result == ebayServer.BID_ERROR_BANNED ||
result == ebayServer.BID_ERROR_WONT_SHIP ||
result == ebayServer.BID_ERROR_REQUIREMENTS_NOT_MET) {
inEntry.setErrorPage(loadedPage);
}
} else {
String amount = htmlDocument.getNextContentAfterRegex("Enter");
if (amount != null) {
String orMore = htmlDocument.getNextContent();
if (orMore != null && orMore.indexOf("or more") != -1) {
result = ebayServer.BID_ERROR_TOO_LOW;
}
}
}
if(JConfig.debugging) inEntry.setLastStatus("Done loading post-bid data.");
if(bidResult != null) return result;
}
// Skipping the userID and Password, so this can be submitted as
// debugging info.
bidForm.setText("user", "HIDDEN");
bidForm.setText("pass", "HIDDEN");
String safeBidInfo = "";
try {
safeBidInfo = bidForm.getCGI();
} catch(UnsupportedEncodingException uee) {
JConfig.log().handleException("UTF-8 not supported locally, can't URLEncode CGI for debugging.", uee);
}
if(JConfig.debugging) inEntry.setLastStatus("Failed to load post-bid data. 'Show Last Error' from context menu to see the failure page from the post-bid page.");
JConfig.log().dump2File("unknown-" + inEntry.getIdentifier() + ".html", loadedPage);
inEntry.setErrorPage(loadedPage);
JConfig.log().logFile(safeBidInfo, loadedPage);
return AuctionServerInterface.BID_ERROR_UNKNOWN;
}
}