/* * This file is part of the OdinMS Maple Story Server Copyright (C) 2008 ~ 2010 * Patrick Huy <patrick.huy@frz.cc> Matthias Butz <matze@odinms.de> Jan * Christian Meyer <vimes@odinms.de> * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by * the Free Software Foundation. You may not use, modify or distribute this * program under any other version of the GNU Affero General Public License. * * 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 Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package javastory.channel.handling; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import javastory.channel.ChannelCharacter; import javastory.channel.ChannelClient; import javastory.db.Database; import javastory.io.PacketFormatException; import javastory.io.PacketReader; import javastory.tools.packets.ChannelPackets; public class BbsHandler { private static String correctLength(final String in, final int maxSize) { if (in.length() > maxSize) { return in.substring(0, maxSize); } return in; } public static void handleBbsOperatopn(final PacketReader reader, final ChannelClient c) throws PacketFormatException { final ChannelCharacter player = c.getPlayer(); if (player.getGuildId() <= 0) { return; // expelled while viewing bbs or hax } int localthreadid = 0; switch (reader.readByte()) { case 0: // start a new post final boolean bEdit = reader.readByte() == 1 ? true : false; if (bEdit) { localthreadid = reader.readInt(); } final boolean bNotice = reader.readByte() == 1 ? true : false; final String title = correctLength(reader.readLengthPrefixedString(), 25); String text = correctLength(reader.readLengthPrefixedString(), 600); final int icon = reader.readInt(); if (icon >= 0x64 && icon <= 0x6a) { if (!player.haveItem(5290000 + icon - 0x64, 1, false, true)) { return; // hax, using an nx icon that s/he doesn't have } } else if (!(icon >= 0 && icon <= 2)) { return; // hax, using an invalid icon } if (!bEdit) { newBbsThread(c, title, text, icon, bNotice); } else { editBbsThread(c, title, text, icon, localthreadid); } break; case 1: // delete a thread localthreadid = reader.readInt(); deleteBbsThread(c, localthreadid); break; case 2: // list threads final int start = reader.readInt(); listBBSThreads(c, start * 10); break; case 3: // list thread + reply, followed by id (int) localthreadid = reader.readInt(); displayThread(c, localthreadid, true); break; case 4: // reply localthreadid = reader.readInt(); text = correctLength(reader.readLengthPrefixedString(), 25); newBbsReply(c, localthreadid, text); break; case 5: // delete reply localthreadid = reader.readInt(); // we don't use this final int replyid = reader.readInt(); deleteBbsReply(c, replyid); break; } } private static void listBBSThreads(final ChannelClient c, final int start) { try { final Connection con = Database.getConnection(); final PreparedStatement ps = con.prepareStatement("SELECT * FROM bbs_threads WHERE guildid = ? ORDER BY localthreadid DESC"); ps.setInt(1, c.getPlayer().getGuildId()); final ResultSet rs = ps.executeQuery(); c.write(ChannelPackets.showBbsThreadList(rs, start)); rs.close(); ps.close(); } catch (final SQLException se) { System.err.println("SQLException: " + se.getLocalizedMessage() + se); } } private static void newBbsReply(final ChannelClient c, final int localthreadid, final String text) { final ChannelCharacter player = c.getPlayer(); if (player.getGuildId() <= 0) { return; } final Connection con = Database.getConnection(); try { PreparedStatement ps = con.prepareStatement("SELECT threadid FROM bbs_threads WHERE guildid = ? AND localthreadid = ?"); ps.setInt(1, player.getGuildId()); ps.setInt(2, localthreadid); final ResultSet threadRS = ps.executeQuery(); if (!threadRS.next()) { threadRS.close(); ps.close(); return; // thread no longer exists, deleted? } final int threadid = threadRS.getInt("threadid"); threadRS.close(); ps.close(); ps = con.prepareStatement("INSERT INTO bbs_replies (`threadid`, `postercid`, `timestamp`, `content`) VALUES " + "(?, ?, ?, ?)"); ps.setInt(1, threadid); ps.setInt(2, player.getId()); ps.setLong(3, System.currentTimeMillis()); ps.setString(4, text); ps.execute(); ps.close(); ps = con.prepareStatement("UPDATE bbs_threads SET replycount = replycount + 1 WHERE threadid = ?"); ps.setInt(1, threadid); ps.execute(); ps.close(); displayThread(c, localthreadid, true); } catch (final SQLException se) { System.err.println("SQLException: " + se.getLocalizedMessage() + se); } } private static void editBbsThread(final ChannelClient c, final String title, final String text, final int icon, final int localThreadId) { final ChannelCharacter player = c.getPlayer(); if (player.getGuildId() <= 0) { return; // expelled while viewing? } final Connection con = Database.getConnection(); try (PreparedStatement ps = con.prepareStatement("UPDATE bbs_threads SET " + "`name` = ?, `timestamp` = ?, " + "`icon` = ?, " + "`startpost` = ? WHERE guildid = ? AND localthreadid = ? AND (postercid = ? OR ?)")) { ps.setString(1, title); ps.setLong(2, System.currentTimeMillis()); ps.setInt(3, icon); ps.setString(4, text); ps.setInt(5, player.getGuildId()); ps.setInt(6, localThreadId); ps.setInt(7, player.getId()); ps.setBoolean(8, player.getGuildRank().isMaster()); ps.execute(); displayThread(c, localThreadId, true); } catch (final SQLException se) { System.err.println("SQLException: " + se.getLocalizedMessage() + se); } } private static void newBbsThread(final ChannelClient c, final String title, final String text, final int icon, final boolean bNotice) { final ChannelCharacter player = c.getPlayer(); if (player.getGuildId() <= 0) { return; // expelled while viewing? } int nextId = 0; try { final Connection con = Database.getConnection(); PreparedStatement ps; if (!bNotice) { // notice's local id is always 0, so we don't need // to fetch it ps = con.prepareStatement("SELECT MAX(localthreadid) AS lastLocalId FROM bbs_threads WHERE guildid = ?"); ps.setInt(1, player.getGuildId()); final ResultSet rs = ps.executeQuery(); rs.next(); nextId = rs.getInt("lastLocalId") + 1; rs.close(); ps.close(); } ps = con.prepareStatement("INSERT INTO bbs_threads (`postercid`, `name`, `timestamp`, `icon`, `startpost`, " + "`guildid`, `localthreadid`) VALUES(?, ?, ?, ?, ?, ?, ?)"); ps.setInt(1, player.getId()); ps.setString(2, title); ps.setLong(3, System.currentTimeMillis()); ps.setInt(4, icon); ps.setString(5, text); ps.setInt(6, player.getGuildId()); ps.setInt(7, nextId); ps.execute(); ps.close(); displayThread(c, nextId, true); } catch (final SQLException se) { System.err.println("SQLException: " + se.getLocalizedMessage() + se); } } private static void deleteBbsThread(final ChannelClient c, final int localthreadid) { final ChannelCharacter player = c.getPlayer(); if (player.getGuildId() <= 0) { return; } final Connection con = Database.getConnection(); try { PreparedStatement ps = con.prepareStatement("SELECT threadid, postercid FROM bbs_threads WHERE guildid = ? AND localthreadid = ?"); ps.setInt(1, player.getGuildId()); ps.setInt(2, localthreadid); final ResultSet threadRS = ps.executeQuery(); if (!threadRS.next()) { threadRS.close(); ps.close(); return; // thread no longer exists, deleted? } if (player.getId() != threadRS.getInt("postercid") && !player.getGuildRank().isMaster()) { threadRS.close(); ps.close(); return; // [hax] deleting a thread that he didn't make } final int threadid = threadRS.getInt("threadid"); threadRS.close(); ps.close(); ps = con.prepareStatement("DELETE FROM bbs_replies WHERE threadid = ?"); ps.setInt(1, threadid); ps.execute(); ps.close(); ps = con.prepareStatement("DELETE FROM bbs_threads WHERE threadid = ?"); ps.setInt(1, threadid); ps.execute(); ps.close(); } catch (final SQLException se) { System.err.println("SQLException: " + se.getLocalizedMessage() + se); } } private static void deleteBbsReply(final ChannelClient c, final int replyid) { final ChannelCharacter player = c.getPlayer(); if (player.getGuildId() <= 0) { return; } int threadid; final Connection con = Database.getConnection(); try { PreparedStatement ps = con.prepareStatement("SELECT postercid, threadid FROM bbs_replies WHERE replyid = ?"); ps.setInt(1, replyid); final ResultSet rs = ps.executeQuery(); if (!rs.next()) { rs.close(); ps.close(); return; // thread no longer exists, deleted? } if (player.getId() != rs.getInt("postercid") && !player.getGuildRank().isMaster()) { rs.close(); ps.close(); return; // [hax] deleting a reply that he didn't make } threadid = rs.getInt("threadid"); rs.close(); ps.close(); ps = con.prepareStatement("DELETE FROM bbs_replies WHERE replyid = ?"); ps.setInt(1, replyid); ps.execute(); ps.close(); ps = con.prepareStatement("UPDATE bbs_threads SET replycount = replycount - 1 WHERE threadid = ?"); ps.setInt(1, threadid); ps.execute(); ps.close(); displayThread(c, threadid, false); } catch (final SQLException se) { System.err.println("SQLException: " + se.getLocalizedMessage() + se); } } private static void displayThread(final ChannelClient c, final int threadid, final boolean bIsThreadIdLocal) { if (c.getPlayer().getGuildId() <= 0) { return; } final Connection con = Database.getConnection(); PreparedStatement ps = null; PreparedStatement ps2 = null; ResultSet repliesRS = null; ResultSet threadRS = null; try { ps = con.prepareStatement("SELECT * FROM bbs_threads WHERE guildid = ? AND " + (bIsThreadIdLocal ? "local" : "") + "threadid = ?"); ps.setInt(1, c.getPlayer().getGuildId()); ps.setInt(2, threadid); threadRS = ps.executeQuery(); if (!threadRS.next()) { threadRS.close(); ps.close(); return; // thread no longer exists, deleted? } if (threadRS.getInt("replycount") > 0) { ps2 = con.prepareStatement("SELECT * FROM bbs_replies WHERE threadid = ?"); ps2.setInt(1, !bIsThreadIdLocal ? threadid : threadRS.getInt("threadid")); repliesRS = ps2.executeQuery(); // the lack of repliesRS.next() is intentional } c.write(ChannelPackets.showThread(bIsThreadIdLocal ? threadid : threadRS.getInt("localthreadid"), threadRS, repliesRS)); } catch (final SQLException se) { System.err.println("SQLException: " + se.getLocalizedMessage() + se); } catch (final RuntimeException re) { System.err.println("The number of reply rows does not match the replycount in thread. ThreadId = " + re.getMessage() + re); try { ps = con.prepareStatement("DELETE FROM bbs_threads WHERE threadid = ?"); ps.setInt(1, Integer.parseInt(re.getMessage())); ps.execute(); ps.close(); ps = con.prepareStatement("DELETE FROM bbs_replies WHERE threadid = ?"); ps.setInt(1, Integer.parseInt(re.getMessage())); ps.execute(); ps.close(); if (ps != null) { ps.close(); } if (repliesRS != null) { repliesRS.close(); } if (threadRS != null) { threadRS.close(); } if (ps2 != null) { ps2.close(); } } catch (final SQLException e) { } } finally { try { if (ps != null) { ps.close(); } if (repliesRS != null) { repliesRS.close(); } if (threadRS != null) { threadRS.close(); } if (ps2 != null) { ps2.close(); } } catch (final SQLException ignore) { } } } }