]> jfr.im git - irc/rizon/acid.git/commitdiff
Implement Vizon
authorOrillion <redacted>
Sun, 16 Apr 2017 19:02:20 +0000 (21:02 +0200)
committerOrillion <redacted>
Sun, 16 Apr 2017 19:02:20 +0000 (21:02 +0200)
38 files changed:
.gitignore
acid/pom.xml
acid/src/main/java/net/rizon/acid/conf/ConfigException.java
acid/src/main/java/net/rizon/acid/core/Acidictive.java
acid/src/main/java/net/rizon/acid/sql/SQL.java
pom.xml
vizon/doc/vizon.mwb [new file with mode: 0644]
vizon/pom.xml [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/Bet.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/BetValidator.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/DrawGenerator.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/DrawingState.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/LotteryThread.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/RequestStatus.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/RequestTracker.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/VhostManager.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/Vizon.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/VizonBet.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/ApproveRequestCommand.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/BetCommand.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/CheckCommand.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/DeleteCommand.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/PendingRequestCommand.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/RejectRequestCommand.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/RequestCommand.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/VipCommand.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/conf/VizonConfig.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/db/VizonDatabase.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/db/VizonDrawing.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/db/VizonRequest.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/db/VizonUser.java [new file with mode: 0644]
vizon/src/main/java/net/rizon/acid/plugins/vizon/util/VizonTemporal.java [new file with mode: 0644]
vizon/src/test/java/net/rizon/acid/plugins/vizon/BetTest.java [new file with mode: 0644]
vizon/src/test/java/net/rizon/acid/plugins/vizon/BetValidatorTest.java [new file with mode: 0644]
vizon/src/test/java/net/rizon/acid/plugins/vizon/DrawGeneratorTest.java [new file with mode: 0644]
vizon/src/test/java/net/rizon/acid/plugins/vizon/VizonDrawingTest.java [new file with mode: 0644]
vizon/vizon.example.yml [new file with mode: 0644]
vizon/vizon.sql [new file with mode: 0644]

index 7b2e3153dc4647355ffbb7360ed1ebb007be590d..b1b2ff16c6b2cc45abee8e353a811b8eb63704a1 100644 (file)
@@ -19,3 +19,4 @@ known_hosts
 .pydev*
 *.iml
 .idea
+*.bak
\ No newline at end of file
index 13af8137c067c51597dc3936de759f242d98c78c..0be26b93201e647fef4465df02057ad9e5be5266 100644 (file)
@@ -97,7 +97,6 @@
                <dependency>
                        <groupId>org.mockito</groupId>
                        <artifactId>mockito-all</artifactId>
-                       <version>1.10.19</version>
                        <scope>test</scope>
                </dependency>
        </dependencies>
index 3f487cdb50d0150d91e1dca75dec96664fe38168..43f70bcde03dcd83dda2215c8988865219afac58 100644 (file)
@@ -3,7 +3,7 @@ package net.rizon.acid.conf;
 @SuppressWarnings("serial")
 public class ConfigException extends Exception
 {
-       ConfigException(String what)
+       public ConfigException(String what)
        {
                super(what);
        }
index fde0f1d42ee844fba7e88364637f565f71b61b1c..c507f414b2a75261fe804ef74cb95a64a3837033 100644 (file)
@@ -374,23 +374,32 @@ public class Acidictive extends AcidCore
                        else
                        {
                                if (!x.hasMode("o"))
-                                       return;
-                               
-                               // Non priv commands can be used by any oper
-                               if (confCommand.privilege == null)
                                {
-                                       log.info("Denied access to " + confCommand.name + " to " + x + " [command has no privilege]");
-                                       // Commands with no priv can not be executed in PM
-                                       return;
+                                       if (!"anyone".equals(confCommand.privilege))
+                                       {
+                                               // Only accept commands that are explicitly accessible
+                                               // by anyone.
+                                               return;
+                                       }
                                }
-
-                               // Explicitly requires no privilege
-                               if (confCommand.privilege.equals("none"))
-                                       ;
-                               else if (x.hasFlags(confCommand.privilege) == false)
+                               else
                                {
-                                       log.info("Denied access to " + confCommand.name + " to " + x + " [user has no priv]");
-                                       return;
+                                       // Non priv commands can be used by any oper
+                                       if (confCommand.privilege == null)
+                                       {
+                                               log.info("Denied access to " + confCommand.name + " to " + x + " [command has no privilege]");
+                                               // Commands with no priv can not be executed in PM
+                                               return;
+                                       }
+
+                                       // Explicitly requires no privilege
+                                       if (confCommand.privilege.equals("none") || "anyone".equals(confCommand.privilege))
+                                               ;
+                                       else if (x.hasFlags(confCommand.privilege) == false)
+                                       {
+                                               log.info("Denied access to " + confCommand.name + " to " + x + " [user has no priv]");
+                                               return;
+                                       }
                                }
                        }
                        
index 562b680ec93fd61c1c610df3c8471cafd3208d64..384f85b0562e0713d398f688ee461f196509291d 100644 (file)
@@ -5,6 +5,7 @@ import java.sql.DriverManager;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.sql.Statement;
 import java.util.LinkedList;
 import net.rizon.acid.conf.Database;
 import net.rizon.acid.core.Acidictive;
@@ -83,6 +84,11 @@ public class SQL extends Thread
        }
 
        public PreparedStatement prepare(final String statement) throws SQLException
+       {
+               return this.prepare(statement, Statement.NO_GENERATED_KEYS);
+       }
+
+       public PreparedStatement prepare(final String statement, int autoGeneratedKeys) throws SQLException
        {
                this.close(this.statement, this.result);
 
@@ -99,7 +105,7 @@ public class SQL extends Thread
                        }
                }
 
-               this.statement = this.con.prepareStatement(statement);
+               this.statement = this.con.prepareStatement(statement, autoGeneratedKeys);
                return this.statement;
        }
 
diff --git a/pom.xml b/pom.xml
index 9362a3c73d82dc1ba4359f490ca98d6082f66fc1..c4e66182617f234cbe481866f968639dd2e1c63d 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -20,6 +20,7 @@
                <module>pyva</module>
                <module>trapbot</module>
                <module>xmas</module>
+               <module>vizon</module>
        </modules>
        
        <dependencyManagement>
                                <version>4.12</version>
                                <scope>test</scope>
                        </dependency>
+                       <dependency>
+                               <groupId>org.mockito</groupId>
+                               <artifactId>mockito-all</artifactId>
+                               <version>1.10.19</version>
+                               <scope>test</scope>
+                       </dependency>
                </dependencies>
        </dependencyManagement>
 
diff --git a/vizon/doc/vizon.mwb b/vizon/doc/vizon.mwb
new file mode 100644 (file)
index 0000000..e651250
Binary files /dev/null and b/vizon/doc/vizon.mwb differ
diff --git a/vizon/pom.xml b/vizon/pom.xml
new file mode 100644 (file)
index 0000000..db32bb7
--- /dev/null
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       
+       <parent>
+               <groupId>net.rizon</groupId>
+               <artifactId>acid</artifactId>
+               <version>4.1-SNAPSHOT</version>
+       </parent>
+
+       <name>VIzon</name>
+       <artifactId>acid-vizon</artifactId>
+
+       <dependencies>
+               <dependency>
+                       <groupId>net.rizon</groupId>
+                       <artifactId>acid-acid</artifactId>
+                       <version>${project.version}</version>
+               </dependency>
+               
+               <dependency>
+                       <groupId>junit</groupId>
+                       <artifactId>junit</artifactId>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.mockito</groupId>
+                       <artifactId>mockito-all</artifactId>
+                       <scope>test</scope>
+               </dependency>
+       </dependencies>
+
+       <build>
+               <plugins>
+                       <plugin>
+                               <artifactId>maven-jar-plugin</artifactId>
+                               <configuration>
+                                       <archive>
+                                               <manifest>
+                                                       <mainClass>net.rizon.acid.plugins.vizon.Vizon</mainClass>
+                                               </manifest>
+                                               <manifestEntries>
+                                                       <Export-Base>net.rizon.acid.plugins.vizon.</Export-Base>
+                                               </manifestEntries>
+                                       </archive>
+                               </configuration>
+                       </plugin>
+               </plugins>
+       </build>
+</project>
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/Bet.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/Bet.java
new file mode 100644 (file)
index 0000000..d6e83d1
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2016, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Holds a user's bet.
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class Bet
+{
+       private final int first;
+       private final int second;
+       private final int third;
+       private final int fourth;
+       private final int fifth;
+       private final int sixth;
+
+       /**
+        * Constructs an object that holds a user's bet.
+        *
+        * @param first  First number the user bet
+        * @param second Second number the user bet
+        * @param third  Third number the user bet
+        * @param fourth Fourth number the user bet
+        * @param fifth  Fifth number the user bet
+        * @param sixth  Sixth number the user bet
+        */
+       public Bet(int first, int second, int third, int fourth, int fifth, int sixth)
+       {
+               this.first = first;
+               this.second = second;
+               this.third = third;
+               this.fourth = fourth;
+               this.fifth = fifth;
+               this.sixth = sixth;
+       }
+
+       /**
+        * Gets the first number the user bet.
+        *
+        * @return First number
+        */
+       public int getFirst()
+       {
+               return first;
+       }
+
+       /**
+        * Gets the second number the user bet.
+        *
+        * @return Second number
+        */
+       public int getSecond()
+       {
+               return second;
+       }
+
+       /**
+        * Gets the third number the user bet.
+        *
+        * @return Third number
+        */
+       public int getThird()
+       {
+               return third;
+       }
+
+       /**
+        * Gets the fourth number the user bet.
+        *
+        * @return Fourth number
+        */
+       public int getFourth()
+       {
+               return fourth;
+       }
+
+       /**
+        * Gets the fifth number the user bet.
+        *
+        * @return Fifth number
+        */
+       public int getFifth()
+       {
+               return fifth;
+       }
+
+       /**
+        * Gets the sixth number the user bet.
+        *
+        * @return Sixth number
+        */
+       public int getSixth()
+       {
+               return sixth;
+       }
+
+       public List<Integer> asList()
+       {
+               return Arrays.asList(
+                               first,
+                               second,
+                               third,
+                               fourth,
+                               fifth,
+                               sixth);
+       }
+
+       public boolean hasSameNumbers(Bet other)
+       {
+               Set<Integer> ours = new HashSet<>(asList()),
+                       theirs = new HashSet<>(other.asList());
+               return ours.equals(theirs);
+       }
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/BetValidator.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/BetValidator.java
new file mode 100644 (file)
index 0000000..6fba2ad
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2016, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Static class which validates a bet placed by a user.
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class BetValidator
+{
+       private static final Pattern BET_PATTERN
+               = Pattern.compile("^\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s*$");
+
+       public static final int BET_MIN = 1;
+       public static final int BET_MAX = 29; // inclusive
+
+       /**
+        * Attempts to validate if a bet placed by a user is within [1,29]. Bets
+        * are done via <code>!bet first second third fourth fifth sixth</code>.
+        * This function expects to get the string without !bet part and then
+        * parses it into an {@link Optional}.
+        *
+        * @param bet Bet the user placed.
+        *
+        * @return {@link Optional} which contains the bet, or is empty if not
+        * parsable.
+        */
+       public static Optional<Bet> validate(String bet)
+       {
+               if (bet == null)
+               {
+                       return Optional.empty();
+               }
+
+               Matcher matcher = BET_PATTERN.matcher(bet);
+
+               if (!matcher.find())
+               {
+                       return Optional.empty();
+               }
+
+               List<Integer> numbers = new ArrayList<>();
+
+               for (int i = 1; i <= matcher.groupCount(); i++)
+               {
+                       // Guaranteed to be integers due to regex.
+                       int number = Integer.parseInt(matcher.group(i));
+
+                       if (!validNumber(number))
+                       {
+                               // Number is not between 1 and 29 inclusive
+                               return Optional.empty();
+                       }
+
+                       if (numbers.contains(number))
+                       {
+                               // Number already occured before
+                               return Optional.empty();
+                       }
+
+                       numbers.add(number);
+               }
+
+               Bet betValue = new Bet(
+                       numbers.get(0),
+                       numbers.get(1),
+                       numbers.get(2),
+                       numbers.get(3),
+                       numbers.get(4),
+                       numbers.get(5));
+
+               return Optional.of(betValue);
+       }
+
+       private static boolean validNumber(int number)
+       {
+               return number >= BET_MIN && number <= BET_MAX;
+       }
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/DrawGenerator.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/DrawGenerator.java
new file mode 100644 (file)
index 0000000..1488e17
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon;
+
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.IntStream;
+import static net.rizon.acid.plugins.vizon.BetValidator.BET_MAX;
+import static net.rizon.acid.plugins.vizon.BetValidator.BET_MIN;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class DrawGenerator
+{
+       private final List<Integer> numbers = new ArrayList<>(BET_MAX - BET_MIN + 1);
+       private final SecureRandom random = new SecureRandom();
+
+       public DrawGenerator()
+       {
+               IntStream
+                       .rangeClosed(BET_MIN, BET_MAX)
+                       .forEach(i -> numbers.add(i));
+       }
+
+       /**
+        * Generates a new drawing with random numbers.
+        *
+        * @return
+        */
+       public Bet generate()
+       {
+               Collections.shuffle(numbers, random);
+
+               Bet bet = new Bet(
+                       numbers.get(0),
+                       numbers.get(1),
+                       numbers.get(2),
+                       numbers.get(3),
+                       numbers.get(4),
+                       numbers.get(5));
+
+               return bet;
+       }
+
+       /**
+        * Takes a random bet and declares it the winner. If there are no bets,
+        * it will generate a random one.
+        *
+        * @param bets Bets that were placed
+        *
+        * @return A random bet.
+        */
+       public Bet generateSpecial(List<Bet> bets)
+       {
+               if (bets == null || bets.isEmpty())
+               {
+                       return generate();
+               }
+
+               Collections.shuffle(bets, random);
+
+               return bets.get(0);
+       }
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/DrawingState.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/DrawingState.java
new file mode 100644 (file)
index 0000000..91c1407
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public enum DrawingState
+{
+       OPEN,
+       CLOSED,
+       PREPARING
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/LotteryThread.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/LotteryThread.java
new file mode 100644 (file)
index 0000000..f091b71
--- /dev/null
@@ -0,0 +1,567 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon;
+
+import net.rizon.acid.plugins.vizon.db.VizonDatabase;
+import net.rizon.acid.plugins.vizon.db.VizonUser;
+import net.rizon.acid.plugins.vizon.db.VizonDrawing;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import static java.util.stream.Collectors.toList;
+import net.rizon.acid.core.AcidUser;
+import net.rizon.acid.core.Acidictive;
+import net.rizon.acid.core.Channel;
+import net.rizon.acid.core.User;
+import net.rizon.acid.util.Format;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class LotteryThread extends Thread
+{
+       private static final Logger logger = LoggerFactory.getLogger(LotteryThread.class);
+
+       private AcidUser vizonUser;
+       private Channel channel;
+       private VizonDatabase database;
+       private VizonDrawing drawing;
+
+       private final List<VizonUser> jackpockWinners = new ArrayList<>();
+       private final List<VizonUser> grandPrizeWinners = new ArrayList<>();
+       private final List<VizonUser> firstPrizeWinners = new ArrayList<>();
+       private final List<VizonUser> secondPrizeWinners = new ArrayList<>();
+       private final List<VizonUser> thirdPrizeWinners = new ArrayList<>();
+       private final List<VizonUser> consolationPrizeWinners = new ArrayList<>();
+
+       public LotteryThread(VizonDrawing drawing)
+       {
+               super("LotteryThread");
+               this.drawing = drawing;
+       }
+
+       @Override
+       public void run()
+       {
+               database = Vizon.getVizonThreadDatabase();
+               channel = Vizon.getVizonChannel();
+               vizonUser = Vizon.getVizonBot();
+
+               boolean channelWasModerated = channel.hasMode('m');
+
+               List<VizonBet> bets = database.findBetsForDrawing(drawing);
+
+               DrawGenerator generator = Vizon.getGenerator();
+               Bet winning;
+
+               if (drawing.getId() % 100 == 0)
+               {
+                       // Do special drawing
+                       List<Bet> userBets = bets.stream()
+                                       .map((b) -> b.getBet())
+                                       .collect(toList());
+
+                       winning = generator.generateSpecial(userBets);
+               }
+               else
+               {
+                       // Do normal drawing
+                       winning = generator.generate();
+               }
+
+               // Set the results of the drawing.
+               drawing.setDrawingResult(winning);
+
+               // Announce we're starting the drawing
+               privmsgChannel(String.format(
+                               "Starting drawing No.%d of VIzon...",
+                               drawing.getId()));
+
+               if (!channelWasModerated)
+               {
+                       // No need to set +m, channel was already moderated
+                       Acidictive.setMode(
+                                       vizonUser.getUID(),
+                                       channel.getName(),
+                                       "+m");
+               }
+
+               // Announce a new number every 5 seconds.
+               for (int i = 1; i <= 6; i++)
+               {
+                       try
+                       {
+                               Thread.sleep(5000);
+
+                               privmsgChannel(String.format(
+                                               "%s number is %2d",
+                                               ordinal(i),
+                                               winning.asList().get(i - 1)));
+                       }
+                       catch (InterruptedException ex)
+                       {
+                               logger.error("LotteryRunner thread was interrupted");
+                               return;
+                       }
+               }
+
+               try
+               {
+                       Thread.sleep(5000);
+               }
+               catch (InterruptedException ex)
+               {
+                       logger.error("LotteryRunner thread was interrupted");
+                       return;
+               }
+
+               // Sort the winning numbers from low to high.
+               List<Integer> winningNumbers = winning.asList();
+               Collections.sort(winningNumbers);
+
+               // Announce results of the drawing, ordered.
+               privmsgChannel(String.format(
+                               "The result of No.%d drawing of VIzon is %d %d %d %d %d %d",
+                               drawing.getId(),
+                               winningNumbers.get(0),
+                               winningNumbers.get(1),
+                               winningNumbers.get(2),
+                               winningNumbers.get(3),
+                               winningNumbers.get(4),
+                               winningNumbers.get(5)));
+
+               // Determine winners
+               for (VizonBet bet : bets)
+               {
+                       int correct = drawing.checkCorrect(bet.getBet());
+
+                       switch (correct)
+                       {
+                               case 6:
+                                       // First prize
+                                       handleFirstPrize(bet);
+                                       break;
+                               case 5:
+                                       // Second prinze
+                                       handleSecondPrize(bet);
+                                       break;
+                               case 4:
+                                       // Third prize
+                                       handleThirdPrize(bet);
+                                       break;
+                               case 3:
+                                       // consolation prize
+                                       handleConsolationPrize(bet);
+                                       break;
+                               default:
+                                       // No prize
+                                       break;
+                       }
+               }
+
+               // Announce we have grand prize winners, if there are any, else
+               // ignore it.
+               if (grandPrizeWinners.size() > 0)
+               {
+                       privmsgChannel(String.format(
+                                       "%cGrand prize: %s%c",
+                                       Format.BOLD,
+                                       winnersString(grandPrizeWinners.size()),
+                                       Format.BOLD));
+               }
+
+               // Announce number of first prize winners.
+               privmsgChannel(String.format(
+                               "%s prize: %s",
+                               ordinal(1),
+                               winnersString(firstPrizeWinners.size())));
+
+               // Announce number of second prize winners.
+               privmsgChannel(String.format(
+                               "%s prize: %s",
+                               ordinal(2),
+                               winnersString(secondPrizeWinners.size())));
+
+               // Announce number of third prize winners.
+               privmsgChannel(String.format(
+                               "%s prize: %s",
+                               ordinal(3),
+                               winnersString(thirdPrizeWinners.size())));
+
+               // Announce number of consolation prize winners.
+               privmsgChannel(String.format(
+                               "Consolation prize: %s",
+                               winnersString(consolationPrizeWinners.size())));
+
+               // Announce number of people that participated
+               privmsgChannel(String.format(
+                               "No. of bets placed: %d",
+                               bets.size()));
+
+               if (!channelWasModerated)
+               {
+                       // Channel was not +m before the drawing began, so we need to reset
+                       // it to -m
+                       Acidictive.setMode(
+                                       vizonUser.getUID(),
+                                       channel.getName(),
+                                       "-m");
+               }
+
+               // Process and notify all winners
+               processWinners();
+
+               database.updateDrawing(drawing);
+
+               Vizon.updateChannel(database);
+       }
+
+       private void processWinners()
+       {
+               for (VizonUser user : jackpockWinners)
+               {
+                       notifyJackpotWinner(user);
+               }
+
+               for (VizonUser user : grandPrizeWinners)
+               {
+                       notifyGrandPrizeWinner(user);
+               }
+
+               for (VizonUser user : firstPrizeWinners)
+               {
+                       notifyFirstPrizeWinner(user);
+               }
+
+               for (VizonUser user : secondPrizeWinners)
+               {
+                       notifySecondPrizeWinner(user);
+               }
+
+               for (VizonUser user : thirdPrizeWinners)
+               {
+                       notifyThirdPrizeWinner(user);
+               }
+
+               for (VizonUser user : consolationPrizeWinners)
+               {
+                       notifyConsolationPrizeWinner(user);
+               }
+       }
+
+       private void notifyJackpotWinner(VizonUser user)
+       {
+               User u = User.findUser(user.getNick());
+
+               notifyUser(
+                               u,
+                               String.format("Congratulations, you won the jackpot in VIzon "
+                                               + "No.%d! You are eligible to select a new vhost and "
+                                               + "won a 1 letter nickname!", drawing.getId()),
+                               user.getNick());
+       }
+
+       private void notifyGrandPrizeWinner(VizonUser user)
+       {
+               User u = User.findUser(user.getNick());
+
+               notifyUser(
+                               u,
+                               String.format("Congratulations, you won the grand prize in VIzon "
+                                               + "No.%d! You are eligible to select a new vhost! Your "
+                                               + "vhost will now be permanent!", drawing.getId()),
+                               user.getNick());
+       }
+
+       private void notifyFirstPrizeWinner(VizonUser user)
+       {
+               User u = User.findUser(user.getNick());
+
+               notifyUser(
+                               u,
+                               String.format("Congratulations, you won the first prize in VIzon "
+                                               + "No.%d! You are eligible to select a new vhost! And your "
+                                               + "vhost will be made bold!", drawing.getId()),
+                               user.getNick());
+       }
+
+       private void notifySecondPrizeWinner(VizonUser user)
+       {
+               User u = User.findUser(user.getNick());
+
+               notifyUser(
+                               u,
+                               String.format("Congratulations, you won the second prize in VIzon "
+                                               + "No.%d! You are eligible to select a new vhost!", drawing.getId()),
+                               user.getNick());
+       }
+
+       private void notifyThirdPrizeWinner(VizonUser user)
+       {
+               User u = User.findUser(user.getNick());
+
+               notifyUser(
+                               u,
+                               String.format("Congratulations, you won the third prize in VIzon "
+                                               + "No.%d! You are eligible to select a new colored vhost!", drawing.getId()),
+                               user.getNick());
+       }
+
+       private void notifyConsolationPrizeWinner(VizonUser user)
+       {
+               User u = User.findUser(user.getNick());
+
+               notifyUser(
+                               u,
+                               String.format("Congratulations, you won the consolation prize in VIzon "
+                                               + "No.%d! You are eligible to select a new colored vhost!", drawing.getId()),
+                               user.getNick());
+       }
+
+       private void notifyUser(User user, String message, String nick)
+       {
+               Acidictive.privmsg(
+                               Vizon.getVizonBot().getNick(),
+                               "MemoServ",
+                               String.format("SEND %s %s", nick, message));
+
+               if (user != null && user.isIdentified())
+               {
+                       Acidictive.privmsg(
+                                       Vizon.getVizonBot().getNick(),
+                                       nick,
+                                       message);
+               }
+       }
+
+       private static String winnersString(int winners)
+       {
+               switch (winners)
+               {
+                       case 0:
+                               return "No winners";
+                       case 1:
+                               return "1 winner";
+                       default:
+                               return String.format("%d winners", winners);
+               }
+       }
+
+       private void handleFirstPrize(VizonBet bet)
+       {
+               VizonUser user = database.findUserById(bet.getUserId());
+
+               if (user == null)
+               {
+                       return;
+               }
+
+               if (user.getObtained() == null)
+               {
+                       user.setObtainedId(bet.getDrawingId());
+                       user.setObtained(LocalDateTime.now());
+               }
+
+               // Allow user to request a new vhost
+               user.setEligible(true);
+               // Set the user's vhost to bold
+               user.setBold(true);
+
+               // @TODO: Determine if we are actually going to use this in the future.
+//             if (user.isPermanent() && !user.isJackpot() && false)
+//             {
+//                     // User has won grand prize before, and is eligible for the jackpot
+//                     jackpockWinners.add(user);
+//             }
+//             else
+               {
+                       // Run normal checks
+                       int multiplier = user.getMultiplier();
+
+                       user.addDays(Vizon.getConf().firstPrize * multiplier);
+
+                       if (user.getDays() >= 180 && !user.isPermanent())
+                       {
+                               // User won grand prize!
+                               // Set +h on user
+                               user.setPermanent(true);
+                               grandPrizeWinners.add(user);
+                       }
+                       else
+                       {
+                               // Set +v on user.
+                               firstPrizeWinners.add(user);
+                       }
+               }
+
+               user.incrementMultiplier();
+               database.updateUser(user);
+       }
+
+       private void handleSecondPrize(VizonBet bet)
+       {
+               VizonUser user = database.findUserById(bet.getUserId());
+
+               if (user == null)
+               {
+                       return;
+               }
+
+               if (user.getObtained() == null)
+               {
+                       user.setObtainedId(bet.getDrawingId());
+                       user.setObtained(LocalDateTime.now());
+               }
+
+               // Allow user to request a new vhost
+               user.setEligible(true);
+
+               int multiplier = user.getMultiplier();
+
+               user.addDays(Vizon.getConf().secondPrize * multiplier);
+
+               if (user.getDays() >= 180 && !user.isPermanent())
+               {
+                       // User won grand prize!
+                       // Set +h on user
+                       user.setPermanent(true);
+                       grandPrizeWinners.add(user);
+               }
+               else
+               {
+                       // Set +v on user.
+                       secondPrizeWinners.add(user);
+               }
+
+               user.incrementMultiplier();
+               database.updateUser(user);
+       }
+
+       private void handleThirdPrize(VizonBet bet)
+       {
+               VizonUser user = database.findUserById(bet.getUserId());
+
+               if (user == null)
+               {
+                       return;
+               }
+
+               if (user.getObtained() == null)
+               {
+                       user.setObtainedId(bet.getDrawingId());
+                       user.setObtained(LocalDateTime.now());
+               }
+
+               // Allow user to request a new vhost
+               user.setEligible(true);
+
+               int multiplier = user.getMultiplier();
+
+               user.addDays(Vizon.getConf().thirdPrize * multiplier);
+
+               if (user.getDays() >= 180 && !user.isPermanent())
+               {
+                       // User won grand prize!
+                       // Set +h on user
+                       user.setPermanent(true);
+                       grandPrizeWinners.add(user);
+               }
+               else
+               {
+                       // Set +v on user.
+                       thirdPrizeWinners.add(user);
+               }
+
+               user.incrementMultiplier();
+               database.updateUser(user);
+       }
+
+       private void handleConsolationPrize(VizonBet bet)
+       {
+               VizonUser user = database.findUserById(bet.getUserId());
+
+               if (user == null)
+               {
+                       return;
+               }
+
+               if (user.getObtained() == null)
+               {
+                       user.setObtainedId(bet.getDrawingId());
+                       user.setObtained(LocalDateTime.now());
+               }
+
+               // Allow user to request a new vhost
+               user.setEligible(true);
+
+               user.addDays(Vizon.getConf().consolationPrize);
+
+               if (user.getDays() >= 180 && !user.isPermanent())
+               {
+                       // User won grand prize!
+                       // Set +h on user
+                       user.setPermanent(true);
+                       grandPrizeWinners.add(user);
+               }
+               else
+               {
+                       // Set +v on user.
+                       consolationPrizeWinners.add(user);
+               }
+
+               database.updateUser(user);
+       }
+
+       private void privmsgChannel(String message)
+       {
+               Acidictive.privmsg(
+                               vizonUser.getUID(),
+                               channel.getName(),
+                               message);
+       }
+
+       private static String ordinal(int i)
+       {
+               String[] sufixes = new String[]
+               {
+                       "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"
+               };
+               switch (i % 100)
+               {
+                       case 11:
+                       case 12:
+                       case 13:
+                               return i + "th";
+                       default:
+                               return i + sufixes[i % 10];
+
+               }
+       }
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/RequestStatus.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/RequestStatus.java
new file mode 100644 (file)
index 0000000..357aa25
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class RequestStatus
+{
+       public static final int PENDING = 0;
+       public static final int APPROVED = 1;
+       public static final int REJECTED = 2;
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/RequestTracker.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/RequestTracker.java
new file mode 100644 (file)
index 0000000..5478546
--- /dev/null
@@ -0,0 +1,247 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon;
+
+import net.rizon.acid.plugins.vizon.db.VizonRequest;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import net.rizon.acid.conf.Config;
+import net.rizon.acid.core.Acidictive;
+import net.rizon.acid.core.Channel;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class RequestTracker
+{
+       public static final String MESSAGE_FORMAT = "[new] %d. %s :: %s\u000F :: Vizon vhost";
+       private static final String REJECT_FORMAT = "SEND %s Your colored vhost %s\u000F has been rejected";
+       private static final String APPROVE_FORMAT = "SEND %s Your colored vhost %s\u000F has been approved";
+       private final List<VizonRequest> requests = new ArrayList<>();
+       private final Config config;
+
+       public RequestTracker(Config conf)
+       {
+               this.config = conf;
+       }
+
+       public void addRequest(VizonRequest request)
+       {
+               Optional<VizonRequest> req = this.requests
+                               .stream()
+                               .filter(r -> r != null)
+                               .filter(r -> r.getId() == request.getId())
+                               .findFirst();
+
+               int index = 0;
+
+               if (req.isPresent())
+               {
+                       index = this.requests.indexOf(req.get());
+                       // Replace old request with new one
+                       this.requests.set(index, request);
+               }
+               else
+               {
+                       // Add new request to list.
+                       this.requests.add(request);
+
+                       index = this.requests.size() - 1;
+               }
+
+               String channel = this.config.getChannelNamed("vhost");
+               Channel c = Channel.findChannel(channel);
+               
+               if (c == null)
+                       return;
+
+               Acidictive.reply(
+                               null,
+                               Vizon.getVizonBot(),
+                               c,
+                               String.format(
+                                               MESSAGE_FORMAT,
+                                               index + 1,
+                                               request.getNick(),
+                                               request.getVhost()));
+       }
+
+       public boolean approveRequest(int request, String oper)
+       {
+               if (request < 0 || request >= this.requests.size())
+               {
+                       return false;
+               }
+
+               VizonRequest req = this.requests.get(request);
+
+               if (req == null)
+               {
+                       return false;
+               }
+
+               req.setOper(oper);
+               req.setStatus(RequestStatus.APPROVED);
+
+               if (!Vizon.getVhostManager().assignVhost(req))
+               {
+                       return false;
+               }
+
+               if (!Vizon.getVizonDatabase().updateRequest(req))
+               {
+                       return false;
+               }
+
+               Acidictive.privmsg(
+                               Vizon.getVizonBot().getNick(),
+                               "MemoServ",
+                               String.format(
+                                               APPROVE_FORMAT,
+                                               req.getNick(),
+                                               req.getVhost()));
+
+               this.requests.set(request, null);
+               this.trimList();
+
+               return true;
+       }
+
+       public boolean rejectRequest(int request, String oper, String reason)
+       {
+               VizonRequest req = this.requests.get(request);
+
+               if (req == null)
+               {
+                       return false;
+               }
+
+               req.setOper(oper);
+               req.setReason(reason);
+               req.setStatus(RequestStatus.REJECTED);
+
+               if (!Vizon.getVizonDatabase().updateRequest(req))
+               {
+                       return false;
+               }
+
+               Acidictive.privmsg(
+                               Vizon.getVizonBot().getNick(),
+                               "MemoServ",
+                               String.format(
+                                               REJECT_FORMAT,
+                                               req.getNick(),
+                                               req.getVhost()));
+
+               this.requests.set(request, null);
+               this.trimList();
+
+               return true;
+       }
+
+       private void trimList()
+       {
+               for (int index = this.requests.size() - 1; index >= 0; index--)
+               {
+                       if (this.requests.get(index) != null)
+                       {
+                               // Clean up list until we find a non-null entry.
+                               break;
+                       }
+
+                       this.requests.remove(index);
+               }
+       }
+
+       public void approveAllRequests(String oper)
+       {
+               for (int i = 0; i < this.requests.size(); i++)
+               {
+                       this.approveRequest(i, oper);
+               }
+       }
+
+       public void rejectAllRequests(String oper)
+       {
+               for (int i = 0; i < this.requests.size(); i++)
+               {
+                       this.rejectRequest(i, oper, null);
+               }
+       }
+
+       public void approveAllRequestsExcept(List<Integer> reqs, String oper)
+       {
+               for (int i = 0; i < this.requests.size(); i++)
+               {
+                       if (reqs.contains(i))
+                       {
+                               this.approveRequest(i, oper);
+                       }
+                       else
+                       {
+                               this.rejectRequest(i, oper, null);
+                       }
+               }
+       }
+
+       public List<VizonRequest> getPending()
+       {
+               Collection<VizonRequest> reqs = Vizon
+                               .getVizonDatabase()
+                               .findPendingVhostRequests();
+
+               if (reqs != null)
+               {
+                       for (VizonRequest req : reqs)
+                       {
+                               Optional<VizonRequest> request = this.requests
+                                               .stream()
+                                               .filter(r -> r != null)
+                                               .filter(r -> r.getId() == req.getId())
+                                               .findFirst();
+
+                               if (request.isPresent())
+                               {
+                                       int index = this.requests.indexOf(request.get());
+                                       // Replace old request with new one
+                                       this.requests.set(index, req);
+                               }
+                               else
+                               {
+                                       // Add new request to list.
+                                       this.requests.add(req);
+
+                               }
+                       }
+               }
+
+               return Collections.unmodifiableList(this.requests);
+       }
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/VhostManager.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/VhostManager.java
new file mode 100644 (file)
index 0000000..5c3a8bb
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon;
+
+import net.rizon.acid.plugins.vizon.db.VizonRequest;
+import net.rizon.acid.plugins.vizon.db.VizonUser;
+import java.util.regex.Pattern;
+import net.rizon.acid.core.Acidictive;
+import net.rizon.acid.core.Protocol;
+import net.rizon.acid.core.User;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class VhostManager
+{
+       private static final Logger logger = LoggerFactory.getLogger(VhostManager.class);
+       private static final String DELETE_FORMAT = "DEL %s";
+
+       public static final Pattern NORMAL_PATTERN = Pattern.compile("^[\u0003\u000Fa-zA-Z0-9-.]*[a-zA-Z]+[\u0003\u000Fa-zA-Z0-9-.]*$");
+       public static final Pattern BOLD_PATTERN = Pattern.compile("^[\u0003\u0002\u000Fa-zA-Z0-9-.]*[a-zA-Z]+[\u0003\u0002\u000Fa-zA-Z0-9-.]*$");
+
+       public VhostManager()
+       {
+
+       }
+
+       public boolean assignVhost(VizonRequest request)
+       {
+               VizonUser user = Vizon.getVizonDatabase().findUserById(request.getUserId());
+
+               if (user == null)
+               {
+                       return false;
+               }
+
+               Acidictive.privmsg("HostServ", String.format(DELETE_FORMAT, user.getNick()));
+
+               user.setVhost(request.getVhost());
+               user.setEligible(false);
+
+               if (!Vizon.getVizonDatabase().updateUser(user))
+               {
+                       return false;
+               }
+
+               User u = User.findUser(request.getNick());
+
+               if (u != null)
+               {
+                       this.applyVhostIfApplicable(u);
+               }
+
+               return true;
+       }
+
+       public void applyVhostIfApplicable(User user)
+       {
+               if (user == null || !user.isIdentified())
+               {
+                       return;
+               }
+
+               VizonUser u = Vizon.getVizonDatabase().findUser(user.getNick());
+
+               if (u == null)
+               {
+                       return;
+               }
+
+               Protocol.chghost(user, u.getVhost());
+               u.setVhost(u.getVhost());
+       }
+
+       public void expireVhosts()
+       {
+               int expired = Vizon.getVizonDatabase().expireVhosts();
+
+               // Users can keep their colored vhosts until they log out, when they expire.
+               logger.info("Expired {} vhosts", expired);
+       }
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/Vizon.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/Vizon.java
new file mode 100644 (file)
index 0000000..ffb462d
--- /dev/null
@@ -0,0 +1,347 @@
+/*
+ * Copyright (c) 2016, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon;
+
+import net.rizon.acid.plugins.vizon.db.VizonDatabase;
+import net.rizon.acid.plugins.vizon.db.VizonUser;
+import net.rizon.acid.plugins.vizon.db.VizonDrawing;
+import com.google.common.eventbus.Subscribe;
+import io.netty.util.concurrent.ScheduledFuture;
+import java.time.LocalDateTime;
+import java.util.concurrent.TimeUnit;
+import net.rizon.acid.conf.Config;
+import net.rizon.acid.conf.ConfigException;
+import net.rizon.acid.core.AcidUser;
+import net.rizon.acid.core.Acidictive;
+import net.rizon.acid.core.Channel;
+import net.rizon.acid.core.User;
+import net.rizon.acid.events.EventJoin;
+import net.rizon.acid.events.EventNickChange;
+import net.rizon.acid.events.EventSync;
+import net.rizon.acid.events.EventUserMode;
+import net.rizon.acid.plugins.Plugin;
+import net.rizon.acid.plugins.vizon.commands.PendingRequestCommand;
+import net.rizon.acid.plugins.vizon.conf.VizonConfig;
+import net.rizon.acid.plugins.vizon.util.VizonTemporal;
+import net.rizon.acid.sql.SQL;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class Vizon extends Plugin
+{
+       private static final Logger logger = LoggerFactory.getLogger(Vizon.class);
+
+       private static VizonConfig conf;
+       private static AcidUser vizonBot;
+       private static Channel vizonChannel;
+       
+       private static SQL vizonSql;
+       private static VizonDatabase vizonDatabase;
+       
+       private static SQL vizonThreadSql;
+       private static VizonDatabase vizonThreadDatabase;
+       
+       private static RequestTracker requestTracker;
+       private static VhostManager vhostManager;
+       private static DrawGenerator generator;
+
+       private ScheduledFuture checkDrawingFuture;
+       private ScheduledFuture vhostExpiryFuture;
+       
+       private LotteryThread lotteryThread;
+
+       public static VizonConfig getConf()
+       {
+               return conf;
+       }
+
+       public static AcidUser getVizonBot()
+       {
+               return vizonBot;
+       }
+
+       public static Channel getVizonChannel()
+       {
+               return vizonChannel;
+       }
+
+       public static SQL getVizonSql()
+       {
+               return vizonSql;
+       }
+
+       public static VizonDatabase getVizonDatabase()
+       {
+               return vizonDatabase;
+       }
+
+       public static VizonDatabase getVizonThreadDatabase()
+       {
+               return vizonThreadDatabase;
+       }
+
+       public static RequestTracker getRequestTracker()
+       {
+               return requestTracker;
+       }
+
+       public static VhostManager getVhostManager()
+       {
+               return vhostManager;
+       }
+
+       public static DrawGenerator getGenerator()
+       {
+               return generator;
+       }
+
+       public static void updateChannel(VizonDatabase database)
+       {
+               for (String nick : vizonChannel.getUsers())
+               {
+                       VizonUser user = database.findUser(nick);
+
+                       if (user == null)
+                       {
+                               continue;
+                       }
+
+                       if (user.isPermanent())
+                       {
+                               // Grand prize winner
+                               Acidictive.setMode(
+                                       vizonBot.getNick(),
+                                       vizonChannel.getName(),
+                                       String.format("+h %s", user.getNick()));
+                       }
+                       else if (user.getObtained() != null)
+                       {
+                               // User won a vhost
+                               Acidictive.setMode(
+                                       vizonBot.getNick(),
+                                       vizonChannel.getName(),
+                                       String.format("+v %s", user.getNick()));
+                       }
+               }
+       }
+
+       private void checkDrawing()
+       {
+               LocalDateTime now = LocalDateTime.now();
+               LocalDateTime drawingDate = VizonTemporal.determineNextDrawing(now, conf);
+
+               VizonDrawing drawing = vizonDatabase.findEarliestUnrunDrawing();
+
+               if (drawing == null)
+               {
+                       drawing = vizonDatabase.createDrawing(drawingDate);
+
+                       if (drawing == null)
+                       {
+                               logger.error("Error creating new drawing");
+                               return;
+                       }
+               }
+
+               logger.debug("Next drawing will be at {}, now is {}", drawing.getDate(), now);
+               
+               if (drawing.getDate().isAfter(now))
+               {
+                       return;
+               }
+               
+               if (lotteryThread != null && lotteryThread.isAlive())
+               {
+                       logger.warn("Unable to start new drawing thread, old one is still alive");
+                       return;
+               }
+               
+               lotteryThread = new LotteryThread(drawing);
+               lotteryThread.start();
+       }
+
+       @Override
+       public void start() throws Exception
+       {
+               reload();
+
+               vizonSql = SQL.getConnection("vizon");
+               vizonThreadSql = SQL.getConnection("vizon");
+               vizonDatabase = new VizonDatabase(vizonSql);
+               vizonThreadDatabase = new VizonDatabase(vizonThreadSql);
+               requestTracker = new RequestTracker(Acidictive.conf);
+               vhostManager = new VhostManager();
+               generator = new DrawGenerator();
+
+               checkDrawingFuture = Acidictive.scheduleAtFixedRate(this::checkDrawing, 1, TimeUnit.MINUTES);
+               vhostExpiryFuture = Acidictive.scheduleAtFixedRate(vhostManager::expireVhosts, 1, TimeUnit.DAYS);
+
+               Acidictive.eventBus.register(this);
+
+               // Load all pending requests from the DB.
+               requestTracker.getPending();
+       }
+
+       @Subscribe
+       public void onNickChange(EventNickChange event)
+       {
+               User user = event.getU();
+
+               if (!user.isIdentified())
+               {
+                       return;
+               }
+
+               vhostManager.applyVhostIfApplicable(user);
+               setChannelModeIfApplicable(user);
+       }
+
+       @Subscribe
+       public void onModeChange(EventUserMode event)
+       {
+               User user = event.getUser();
+
+               if (!event.getNewmodes().contains("r"))
+               {
+                       return;
+               }
+
+               vhostManager.applyVhostIfApplicable(user);
+               setChannelModeIfApplicable(user);
+       }
+
+       @Subscribe
+       public void onChannelJoin(EventJoin event)
+       {
+               Channel channel = event.getChannel();
+
+               if (!vizonChannel.equals(channel))
+               {
+                       return;
+               }
+
+               for (User user : event.getUsers())
+               {
+                       setChannelModeIfApplicable(user);
+               }
+       }
+
+       private void setChannelModeIfApplicable(User user)
+       {
+               if (!user.isIdentified())
+               {
+                       return;
+               }
+
+               VizonUser vu = Vizon.getVizonDatabase().findUser(user.getNick());
+
+               if (vu == null || vu.getObtained() == null)
+               {
+                       return;
+               }
+
+               String mode = vu.isPermanent() ? "+h" : "+v";
+
+               Acidictive.setMode(
+                       vizonBot.getNick(),
+                       vizonChannel.getName(),
+                       String.format(
+                               "%s %s",
+                               mode,
+                               user.getNick()));
+       }
+
+       @Subscribe
+       public void onSync(EventSync event)
+       {
+               // Run pending command on start.
+               PendingRequestCommand c = new PendingRequestCommand();
+               String channel = Acidictive.conf.getChannelNamed("vhost");
+               Channel chan = Channel.findChannel(channel);
+               
+               if (chan == null)
+                       return;
+               
+               c.Run(vizonBot, vizonBot, chan, new String[0]);
+
+               Vizon.updateChannel(vizonDatabase);
+       }
+
+       @Override
+       public void stop()
+       {
+               if (checkDrawingFuture != null)
+               {
+                       checkDrawingFuture.cancel(true);
+                       checkDrawingFuture = null;
+               }
+               
+               if (vhostExpiryFuture != null)
+               {
+                       vhostExpiryFuture.cancel(true);
+                       vhostExpiryFuture = null;
+               }
+               
+               vizonSql.shutdown();
+               vizonThreadSql.shutdown();
+
+               Acidictive.eventBus.unregister(this);
+       }
+
+       @Override
+       public void reload() throws Exception
+       {
+               conf = (VizonConfig) Config.load("vizon.yml", VizonConfig.class);
+               Acidictive.loadClients(this, conf.clients);
+
+               User u = User.findUser(conf.vizonBot);
+
+               if (u == null || !(u instanceof AcidUser))
+               {
+                       throw new ConfigException("VizonBot not loaded, or its nickname is in use by someone else");
+               }
+
+               Channel channel = Channel.findChannel(conf.vizonChannel);
+
+               if (channel == null)
+               {
+                       throw new ConfigException("VizonChannel does not exist");
+               }
+
+               vizonBot = (AcidUser) u;
+
+               if (!vizonBot.isOnChan(channel))
+               {
+                       throw new ConfigException("VizonBot not in Vizon channel");
+               }
+
+               vizonChannel = channel;
+       }
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/VizonBet.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/VizonBet.java
new file mode 100644 (file)
index 0000000..c8c4ec7
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class VizonBet
+{
+       private final int id;
+       private final int userId;
+       private final int drawingId;
+       private final Bet bet;
+       private final LocalDateTime placed;
+
+       public static VizonBet fromResultSet(ResultSet rs)
+       {
+               try
+               {
+                       int id = rs.getInt("id");
+                       int userId = rs.getInt("vizon_users_id");
+                       int drawingId = rs.getInt("vizon_drawings_id");
+
+                       int first = rs.getInt("first");
+                       int second = rs.getInt("second");
+                       int third = rs.getInt("third");
+                       int fourth = rs.getInt("fourth");
+                       int fifth = rs.getInt("fifth");
+                       int sixth = rs.getInt("sixth");
+
+                       Bet bet = new Bet(first, second, third, fourth, fifth, sixth);
+
+                       Timestamp ts = rs.getTimestamp("placed");
+
+                       return new VizonBet(id, userId, drawingId, bet, ts.toLocalDateTime());
+               }
+               catch (SQLException ex)
+               {
+                       return null;
+               }
+       }
+
+       private VizonBet(int id, int userId, int drawingId, Bet bet, LocalDateTime placed)
+       {
+               this.id = id;
+               this.userId = userId;
+               this.drawingId = drawingId;
+               this.bet = bet;
+               this.placed = placed;
+       }
+
+       public int getId()
+       {
+               return id;
+       }
+
+       public int getUserId()
+       {
+               return userId;
+       }
+
+       public int getDrawingId()
+       {
+               return drawingId;
+       }
+
+       public Bet getBet()
+       {
+               return bet;
+       }
+
+       public LocalDateTime getPlaced()
+       {
+               return placed;
+       }
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/ApproveRequestCommand.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/ApproveRequestCommand.java
new file mode 100644 (file)
index 0000000..c58e54a
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon.commands;
+
+import net.rizon.acid.core.AcidUser;
+import net.rizon.acid.core.Acidictive;
+import net.rizon.acid.core.Channel;
+import net.rizon.acid.core.Command;
+import net.rizon.acid.core.User;
+import net.rizon.acid.plugins.vizon.Vizon;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class ApproveRequestCommand extends Command
+{
+       public ApproveRequestCommand()
+       {
+               super(1, 1);
+       }
+
+       @Override
+       public void Run(User source, AcidUser to, Channel c, String[] args)
+       {
+               try
+               {
+                       int index = Integer.parseInt(args[0]);
+
+                       if (Vizon.getRequestTracker().approveRequest(index - 1, source.getNick()))
+                       {
+                               Acidictive.reply(source, to, c, "Vhost approved");
+                       }
+                       else
+                       {
+                               Acidictive.reply(source, to, c, "Unable to approve vhost for id: " + index);
+                       }
+               }
+               catch (NumberFormatException ex)
+               {
+                       Acidictive.reply(source, to, c, "Argument is not a number");
+               }
+       }
+
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/BetCommand.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/BetCommand.java
new file mode 100644 (file)
index 0000000..ad4dc91
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2016, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon.commands;
+
+import java.util.Optional;
+import net.rizon.acid.core.AcidUser;
+import net.rizon.acid.core.Acidictive;
+import net.rizon.acid.core.Channel;
+import net.rizon.acid.core.Command;
+import net.rizon.acid.core.User;
+import net.rizon.acid.plugins.vizon.Bet;
+import net.rizon.acid.plugins.vizon.BetValidator;
+import net.rizon.acid.plugins.vizon.Vizon;
+import net.rizon.acid.plugins.vizon.VizonBet;
+import net.rizon.acid.plugins.vizon.db.VizonDrawing;
+import net.rizon.acid.plugins.vizon.db.VizonUser;
+
+/**
+ * This class handles the user's Bet command.
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class BetCommand extends Command
+{
+       public BetCommand()
+       {
+               super(1, 1);
+       }
+
+       @Override
+       public void Run(User source, AcidUser to, Channel c, String[] args)
+       {
+               // @TODO: Perhaps we should move all replies to a config setting?
+               if (!source.isIdentified())
+               {
+                       // User has not identified to NickServ.
+                       Acidictive.reply(source, to, c, "You need to identify before you can participate");
+                       return;
+               }
+
+               if (!source.isOnChan(Vizon.getVizonChannel()))
+               {
+                       // User is not on the VIzon channel.
+                       Acidictive.reply(source, to, c, String.format("You need to be on %s before placing a bet", Vizon.getConf().vizonChannel));
+                       return;
+               }
+
+               VizonDrawing drawing = Vizon.getVizonDatabase().getNextDrawing();
+
+               if (drawing == null)
+               {
+                       // @TODO: Tell user when bet will open.
+                       Acidictive.reply(source, to, c,
+                                       "Betting is not open yet");
+                       return;
+               }
+
+               switch (drawing.getState())
+               {
+                       case PREPARING:
+                               Acidictive.reply(source, to, c,
+                                               "Betting is not open yet");
+                               return;
+                       case CLOSED:
+                               Acidictive.reply(source, to, c,
+                                               "Betting is closed, drawing will begin shortly");
+                               return;
+                       case OPEN:
+                       default:
+                               break;
+               }
+
+               // Find or create the user.
+               VizonUser user = Vizon.getVizonDatabase().findOrCreateUser(source.getNick());
+
+               if (user == null)
+               {
+                       Acidictive.reply(source, to, c,
+                                       "Unable to find or create a user object");
+                       return;
+               }
+
+               VizonBet vizonBet = Vizon.getVizonDatabase().findBetForUserAndDrawing(user, drawing);
+
+               if (vizonBet != null)
+               {
+                       Acidictive.reply(source, to, c,
+                                       String.format("You have already placed a bet, your bet is %d %d %d %d %d %d",
+                                                       vizonBet.getBet().getFirst(),
+                                                       vizonBet.getBet().getSecond(),
+                                                       vizonBet.getBet().getThird(),
+                                                       vizonBet.getBet().getFourth(),
+                                                       vizonBet.getBet().getFifth(),
+                                                       vizonBet.getBet().getSixth()));
+                       return;
+               }
+
+               Optional<Bet> betOpt = BetValidator.validate(args[0]);
+
+               if (!betOpt.isPresent())
+               {
+                       // User made an invalid bet.
+                       // @TODO: Move reply to config setting.
+                       Acidictive.reply(source, to, c,
+                                       "Invalid bet. Please make sure you pick 6 non-repeating numbers between 1 and 29. Read http://s.rizon.net/VIzon for details.");
+                       return;
+               }
+
+               Bet bet = betOpt.get();
+
+               boolean success = Vizon.getVizonDatabase().createBetForUser(user, drawing, bet);
+
+               if (success)
+               {
+                       // Notify user bet was stored.
+                       Acidictive.reply(
+                                       source,
+                                       to,
+                                       c,
+                                       String.format("Bet placed: %d %d %d %d %d %d",
+                                                       bet.getFirst(),
+                                                       bet.getSecond(),
+                                                       bet.getThird(),
+                                                       bet.getFourth(),
+                                                       bet.getFifth(),
+                                                       bet.getSixth()));
+               }
+               else
+               {
+                       // Notify user that we failed to save the bet in the database.
+                       Acidictive.reply(source, to, c,
+                                       "Failed to store bet, if this problem persists, please contact a staff member");
+               }
+       }
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/CheckCommand.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/CheckCommand.java
new file mode 100644 (file)
index 0000000..ff9b091
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon.commands;
+
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.List;
+import net.rizon.acid.core.AcidUser;
+import net.rizon.acid.core.Acidictive;
+import net.rizon.acid.core.Channel;
+import net.rizon.acid.core.Command;
+import net.rizon.acid.core.User;
+import net.rizon.acid.plugins.vizon.Vizon;
+import net.rizon.acid.plugins.vizon.VizonBet;
+import net.rizon.acid.plugins.vizon.db.VizonDrawing;
+import net.rizon.acid.plugins.vizon.db.VizonUser;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class CheckCommand extends Command
+{
+       public CheckCommand()
+       {
+               super(1, 1);
+       }
+
+       @Override
+       public void Run(User source, AcidUser to, Channel c, String[] args)
+       {
+               if (!source.hasMode("r"))
+               {
+                       // User has not identified to NickServ.
+                       Acidictive.reply(source, to, c, "You need to register your nickname before you can use this command");
+                       return;
+               }
+
+               int drawingId;
+
+               try
+               {
+                       drawingId = Integer.parseInt(args[0]);
+               }
+               catch (NumberFormatException e)
+               {
+                       Acidictive.reply(source, to, c, "Please specify the number of the drawing you wish to check");
+                       return;
+               }
+
+               VizonDrawing drawing = Vizon.getVizonDatabase().getDrawingById(drawingId);
+
+               if (drawing == null)
+               {
+                       Acidictive.reply(source, to, c, String.format("Drawing with id %d does not exist", drawingId));
+                       return;
+               }
+
+               VizonUser user = Vizon.getVizonDatabase().findOrCreateUser(source.getNick());
+
+               if (user == null)
+               {
+                       // Should not happen, but just in case.
+                       Acidictive.reply(source, to, c, "Unable to find your name in the database");
+                       return;
+               }
+
+               VizonBet bet = Vizon.getVizonDatabase().findBetForUserAndDrawing(user, drawing);
+
+               if (drawing.getDate().isAfter(LocalDateTime.now()))
+               {
+                       if (bet == null)
+                       {
+                               Acidictive.reply(source, to, c, String.format(
+                                               "Drawing No.%d did not happen yet!",
+                                               drawingId));
+                       }
+                       else
+                       {
+                               Acidictive.reply(source, to, c, String.format(
+                                               "Drawing No.%d did not happen yet! Your bet is: %d %d %d %d %d %d",
+                                               drawingId,
+                                               bet.getBet().getFirst(),
+                                               bet.getBet().getSecond(),
+                                               bet.getBet().getThird(),
+                                               bet.getBet().getFourth(),
+                                               bet.getBet().getFifth(),
+                                               bet.getBet().getSixth()));
+                       }
+
+                       return;
+               }
+
+               List<Integer> bets = drawing.getDraws();
+               Collections.sort(bets);
+
+               if (bet == null)
+               {
+                       Acidictive.reply(source, to, c, String.format(
+                                       "The result of VIzon No.%d was: %d %d %d %d %d %d, you did not place a bet for this drawing",
+                                       drawing.getId(),
+                                       bets.get(0),
+                                       bets.get(1),
+                                       bets.get(2),
+                                       bets.get(3),
+                                       bets.get(4),
+                                       bets.get(5)));
+               }
+               else
+               {
+                       // @TODO: Figure out how to recover Grand Prize info, perhaps
+                       //        store in the database or something.
+                       int correct = drawing.checkCorrect(bet.getBet());
+
+                       switch (correct)
+                       {
+                               case 6:
+                                       Acidictive.reply(source, to, c, String.format(
+                                                       "The result of VIzon No.%d was: %d %d %d %d %d %d, you won first prize for this drawing! Your bet was: %d %d %d %d %d %d",
+                                                       drawing.getId(),
+                                                       bets.get(0),
+                                                       bets.get(1),
+                                                       bets.get(2),
+                                                       bets.get(3),
+                                                       bets.get(4),
+                                                       bets.get(5),
+                                                       bet.getBet().getFirst(),
+                                                       bet.getBet().getSecond(),
+                                                       bet.getBet().getThird(),
+                                                       bet.getBet().getFourth(),
+                                                       bet.getBet().getFifth(),
+                                                       bet.getBet().getSixth()));
+                                       break;
+                               case 5:
+                                       Acidictive.reply(source, to, c, String.format(
+                                                       "The result of VIzon No.%d was: %d %d %d %d %d %d, you won second prize for this drawing! Your bet was: %d %d %d %d %d %d",
+                                                       drawing.getId(),
+                                                       bets.get(0),
+                                                       bets.get(1),
+                                                       bets.get(2),
+                                                       bets.get(3),
+                                                       bets.get(4),
+                                                       bets.get(5),
+                                                       bet.getBet().getFirst(),
+                                                       bet.getBet().getSecond(),
+                                                       bet.getBet().getThird(),
+                                                       bet.getBet().getFourth(),
+                                                       bet.getBet().getFifth(),
+                                                       bet.getBet().getSixth()));
+                                       break;
+                               case 4:
+                                       Acidictive.reply(source, to, c, String.format(
+                                                       "The result of VIzon No.%d was: %d %d %d %d %d %d, you won third prize for this drawing! Your bet was: %d %d %d %d %d %d",
+                                                       drawing.getId(),
+                                                       bets.get(0),
+                                                       bets.get(1),
+                                                       bets.get(2),
+                                                       bets.get(3),
+                                                       bets.get(4),
+                                                       bets.get(5),
+                                                       bet.getBet().getFirst(),
+                                                       bet.getBet().getSecond(),
+                                                       bet.getBet().getThird(),
+                                                       bet.getBet().getFourth(),
+                                                       bet.getBet().getFifth(),
+                                                       bet.getBet().getSixth()));
+                                       break;
+                               case 3:
+                                       Acidictive.reply(source, to, c, String.format(
+                                                       "The result of VIzon No.%d was: %d %d %d %d %d %d, you won the consolation prize for this drawing! Your bet was: %d %d %d %d %d %d",
+                                                       drawing.getId(),
+                                                       bets.get(0),
+                                                       bets.get(1),
+                                                       bets.get(2),
+                                                       bets.get(3),
+                                                       bets.get(4),
+                                                       bets.get(5),
+                                                       bet.getBet().getFirst(),
+                                                       bet.getBet().getSecond(),
+                                                       bet.getBet().getThird(),
+                                                       bet.getBet().getFourth(),
+                                                       bet.getBet().getFifth(),
+                                                       bet.getBet().getSixth()));
+                                       break;
+                               default:
+                                       Acidictive.reply(source, to, c, String.format(
+                                                       "The result of VIzon No.%d was: %d %d %d %d %d %d, you did not win any prize for this drawing. Your bet was: %d %d %d %d %d %d",
+                                                       drawing.getId(),
+                                                       bets.get(0),
+                                                       bets.get(1),
+                                                       bets.get(2),
+                                                       bets.get(3),
+                                                       bets.get(4),
+                                                       bets.get(5),
+                                                       bet.getBet().getFirst(),
+                                                       bet.getBet().getSecond(),
+                                                       bet.getBet().getThird(),
+                                                       bet.getBet().getFourth(),
+                                                       bet.getBet().getFifth(),
+                                                       bet.getBet().getSixth()));
+                                       break;
+                       }
+               }
+       }
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/DeleteCommand.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/DeleteCommand.java
new file mode 100644 (file)
index 0000000..b44d292
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon.commands;
+
+import net.rizon.acid.core.AcidUser;
+import net.rizon.acid.core.Acidictive;
+import net.rizon.acid.core.Channel;
+import net.rizon.acid.core.Command;
+import net.rizon.acid.core.Protocol;
+import net.rizon.acid.core.User;
+import net.rizon.acid.plugins.vizon.Vizon;
+import net.rizon.acid.plugins.vizon.db.VizonUser;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class DeleteCommand extends Command
+{
+       public DeleteCommand()
+       {
+               super(1, 1);
+       }
+
+       @Override
+       public void Run(User source, AcidUser to, Channel c, String[] args)
+       {
+               String nick = args[0];
+
+               VizonUser user = Vizon.getVizonDatabase().findUser(nick);
+
+               if (user == null)
+               {
+                       Acidictive.reply(source, to, c, "Nickname not found in database");
+                       return;
+               }
+
+               user.setEligible(false);
+               user.setVhost(null);
+               user.setBold(false);
+               user.setPermanent(false);
+               user.setJackpot(false);
+
+               if (Vizon.getVizonDatabase().updateUser(user))
+               {
+                       Acidictive.reply(source, to, c, String.format("Removed colored vhost for user %s", nick));
+               }
+               else
+               {
+                       Acidictive.reply(source, to, c, "Unable to update user, contact dev team");
+               }
+
+               User ircuser = User.findUser(nick);
+
+               if (ircuser == null)
+               {
+                       return;
+               }
+
+               Protocol.chghost(ircuser, ircuser.getCloakedHost());
+               ircuser.setVhost(ircuser.getCloakedHost());
+       }
+
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/PendingRequestCommand.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/PendingRequestCommand.java
new file mode 100644 (file)
index 0000000..67773dd
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon.commands;
+
+import java.util.List;
+import net.rizon.acid.core.AcidUser;
+import net.rizon.acid.core.Acidictive;
+import net.rizon.acid.core.Channel;
+import net.rizon.acid.core.Command;
+import net.rizon.acid.core.User;
+import net.rizon.acid.plugins.vizon.RequestTracker;
+import net.rizon.acid.plugins.vizon.Vizon;
+import net.rizon.acid.plugins.vizon.db.VizonRequest;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class PendingRequestCommand extends Command
+{
+       public PendingRequestCommand()
+       {
+               super(0, 0);
+       }
+
+       @Override
+       public void Run(User source, AcidUser to, Channel c, String[] args)
+       {
+               List<VizonRequest> requests = Vizon.getRequestTracker().getPending();
+
+               if (requests.isEmpty())
+               {
+                       Acidictive.reply(source, to, c, "No pending colored vhosts");
+                       return;
+               }
+
+               for (int index = 0; index < requests.size(); index++)
+               {
+                       VizonRequest request = requests.get(index);
+
+                       if (request == null)
+                       {
+                               continue;
+                       }
+
+                       Acidictive.reply(source, to, c, String.format(
+                                       RequestTracker.MESSAGE_FORMAT,
+                                       index + 1,
+                                       request.getNick(),
+                                       request.getVhost()));
+               }
+       }
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/RejectRequestCommand.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/RejectRequestCommand.java
new file mode 100644 (file)
index 0000000..6b7d4f9
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon.commands;
+
+import net.rizon.acid.core.AcidUser;
+import net.rizon.acid.core.Acidictive;
+import net.rizon.acid.core.Channel;
+import net.rizon.acid.core.Command;
+import net.rizon.acid.core.User;
+import net.rizon.acid.plugins.vizon.Vizon;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class RejectRequestCommand extends Command
+{
+       public RejectRequestCommand()
+       {
+               super(1, 2);
+       }
+
+       @Override
+       public void Run(User source, AcidUser to, Channel c, String[] args)
+       {
+               String reason = null;
+
+               if (args.length > 1)
+               {
+                       reason = args[1];
+               }
+
+               try
+               {
+                       int index = Integer.parseInt(args[0]);
+
+                       if (Vizon.getRequestTracker().rejectRequest(index - 1, source.getNick(), reason))
+                       {
+                               Acidictive.reply(source, to, c, "Vhost rejected");
+                       }
+                       else
+                       {
+                               Acidictive.reply(source, to, c, "Unable to reject vhost for id: " + index);
+                       }
+               }
+               catch (NumberFormatException ex)
+               {
+                       Acidictive.reply(source, to, c, "Argument is not a number");
+               }
+       }
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/RequestCommand.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/RequestCommand.java
new file mode 100644 (file)
index 0000000..d13da07
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon.commands;
+
+import java.time.LocalDateTime;
+import java.util.regex.Matcher;
+import net.rizon.acid.core.AcidUser;
+import net.rizon.acid.core.Acidictive;
+import net.rizon.acid.core.Channel;
+import net.rizon.acid.core.Command;
+import net.rizon.acid.core.User;
+import net.rizon.acid.plugins.vizon.RequestStatus;
+import net.rizon.acid.plugins.vizon.VhostManager;
+import net.rizon.acid.plugins.vizon.Vizon;
+import net.rizon.acid.plugins.vizon.db.VizonRequest;
+import net.rizon.acid.plugins.vizon.db.VizonUser;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class RequestCommand extends Command
+{
+       private static final int MAX_HOST_BYTES = 63;
+
+       public RequestCommand()
+       {
+               super(1, 1);
+       }
+
+       @Override
+       public void Run(User source, AcidUser to, Channel c, String[] args)
+       {
+               String vhost = args[0] + '\u000F';
+
+               if (!source.isIdentified())
+               {
+                       // User has not identified to NickServ.
+                       Acidictive.reply(source, to, c, "You need to register or identify to your nickname before you can use this command");
+                       return;
+               }
+
+               if (c != null)
+               {
+                       // Cannot be a channel command
+                       Acidictive.reply(source, to, null, "This command cannot be used in a channel");
+                       return;
+               }
+
+               VizonUser user = Vizon.getVizonDatabase().findOrCreateUser(source.getNick());
+               
+               if (!user.isEligible())
+               {
+                       // User not eligible to select a new vhost
+                       Acidictive.reply(source, to, null, "You are not allowed to select a new vhost");
+                       return;
+               }
+
+               Matcher matcher;
+
+               if (user.isBold())
+               {
+                       matcher = VhostManager.BOLD_PATTERN.matcher(vhost);
+               }
+               else
+               {
+                       matcher = VhostManager.NORMAL_PATTERN.matcher(vhost);
+               }
+
+               if (!matcher.matches())
+               {
+                       // Vhost contains illegal characters
+                       Acidictive.reply(source, to, null, "Requested vhost contains illegal characters, or is empty");
+                       return;
+               }
+
+               if (vhost.getBytes().length > MAX_HOST_BYTES)
+               {
+                       // We count bytes instead of chars, since counting chars is wrong.
+                       Acidictive.reply(source, to, null, "Requested vhost is too long");
+                       return;
+               }
+               
+               VizonRequest request = Vizon.getVizonDatabase().findVhostRequestByUserId(user.getId());
+
+               if (request == null)
+               {
+                       // id doesn't really matter, it will get assigned in the database automatically.
+                       request = new VizonRequest(
+                                       -1,
+                                       user.getId(),
+                                       user.getNick(),
+                                       vhost,
+                                       RequestStatus.PENDING,
+                                       null,
+                                       null,
+                                       LocalDateTime.now());
+
+                       if (!Vizon.getVizonDatabase().insertRequest(request))
+                       {
+                               // User not eligible to select a new vhost
+                               Acidictive.reply(source, to, null, "Something went wrong while registering your vhost request, please try again or contact an operator");
+                               return;
+                       }
+
+                       request = Vizon.getVizonDatabase().findVhostRequestByUserId(request.getUserId());
+
+                       Acidictive.reply(source, to, null, "Vhost requested");
+
+                       if (request != null)
+                       {
+                               // Should never be null, but you know.
+                               Vizon.getRequestTracker().addRequest(request);
+                       }
+               }
+               else if (request.getVhost().equals(vhost))
+               {
+                       Acidictive.reply(source, to, null, "You already requested this vhost");
+               }
+               else
+               {
+                       request.setVhost(vhost);
+
+                       if (!Vizon.getVizonDatabase().updateRequest(request))
+                       {
+                               Acidictive.reply(source, to, null, "Something went wrong while updating your vhost request, please try again or contact an operator");
+                               return;
+                       }
+
+                       Acidictive.reply(source, to, null, "Request updated");
+
+                       Vizon.getRequestTracker().addRequest(request);
+               }
+       }
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/VipCommand.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/commands/VipCommand.java
new file mode 100644 (file)
index 0000000..0551edb
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon.commands;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+import net.rizon.acid.core.AcidUser;
+import net.rizon.acid.core.Acidictive;
+import net.rizon.acid.core.Channel;
+import net.rizon.acid.core.Command;
+import net.rizon.acid.core.User;
+import net.rizon.acid.plugins.vizon.Vizon;
+import net.rizon.acid.plugins.vizon.db.VizonUser;
+import net.rizon.acid.util.Util;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class VipCommand extends Command
+{
+       private static final String VIP_FORMAT = "Multiplication factor: %d; Total accumulated days of current VIP: %d; Last drawing claimed of current VIP: %s (No.%d); Remaining time of current VIP: %s";
+
+       public VipCommand()
+       {
+               super(0, 0);
+       }
+
+       @Override
+       public void Run(User source, AcidUser to, Channel c, String[] args)
+       {
+               if (!source.isIdentified())
+               {
+                       // User not identified
+                       Acidictive.reply(source, to, c, "You need to identify before you can use this command");
+                       return;
+               }
+
+               VizonUser user = Vizon.getVizonDatabase().findUser(source.getNick());
+
+               if (user == null)
+               {
+                       // User never played
+                       Acidictive.reply(source, to, c, "You have never played in Vizon before!");
+                       return;
+               }
+
+               if (user.getObtained() == null)
+               {
+                       // Not in a VIP session
+                       Acidictive.reply(source, to, c, "You have no VIP");
+                       return;
+               }
+
+               Duration remaining = Duration.between(LocalDateTime.now(), user.getObtained().plusDays(user.getDays()));
+               String remainingTime = Util.fTime((int) remaining.getSeconds());
+
+               Acidictive.reply(source, to, c, String.format(
+                               VIP_FORMAT,
+                               user.getMultiplier(),
+                               user.getDays(),
+                               user.getObtained(),
+                               user.getObtainedId(),
+                               remainingTime));
+       }
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/conf/VizonConfig.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/conf/VizonConfig.java
new file mode 100644 (file)
index 0000000..765e45c
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon.conf;
+
+import java.time.Duration;
+import java.util.List;
+import net.rizon.acid.arguments.ExpiryArgument;
+import net.rizon.acid.conf.Client;
+import net.rizon.acid.conf.ConfigException;
+import net.rizon.acid.conf.Configuration;
+import net.rizon.acid.conf.Validator;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class VizonConfig extends Configuration
+{
+       public List<Client> clients;
+       public String vizonChannel;
+       public String vizonBot;
+       public List<Integer> days;
+       public String drawingOpen;
+       public String drawingClose;
+       public String drawingTime;
+
+       public int firstPrize;
+       public int secondPrize;
+       public int thirdPrize;
+       public int consolationPrize;
+
+       private ExpiryArgument drawingOpenArgument;
+       private ExpiryArgument drawingCloseArgument;
+
+       @Override
+       public void validate() throws ConfigException
+       {
+               drawingOpenArgument = ExpiryArgument.parse(drawingOpen);
+               drawingCloseArgument = ExpiryArgument.parse(drawingClose);
+
+               Validator.validateNotNull("drawingOpenArgument", drawingOpenArgument);
+               Validator.validateNotNull("drawingCloseArgument", drawingCloseArgument);
+               Validator.validateNotNull("days", days);
+       }
+
+       public Duration getOpenTimeInterval()
+       {
+               return drawingOpenArgument.getDuration();
+       }
+
+       public Duration getCloseTimeInterval()
+       {
+               return drawingCloseArgument.getDuration();
+       }
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/db/VizonDatabase.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/db/VizonDatabase.java
new file mode 100644 (file)
index 0000000..36bf4d2
--- /dev/null
@@ -0,0 +1,772 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon.db;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import net.rizon.acid.plugins.vizon.Bet;
+import net.rizon.acid.plugins.vizon.RequestStatus;
+import net.rizon.acid.plugins.vizon.VizonBet;
+import net.rizon.acid.sql.SQL;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class VizonDatabase
+{
+       private static final Logger logger = LoggerFactory.getLogger(VizonDatabase.class);
+       private final SQL vizonSql;
+
+       public VizonDatabase(SQL vizonSql)
+       {
+               this.vizonSql = vizonSql;
+       }
+
+       /**
+        * Finds a drawing by its id.
+        *
+        * @param id Id of the drawing.
+        *
+        * @return {@link VizonDrawing} or null if not found.
+        */
+       public VizonDrawing getDrawingById(int id)
+       {
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("SELECT * FROM vizon_drawings "
+                                       + "WHERE id = ?");
+
+                       statement.setInt(1, id);
+
+                       ResultSet rs = vizonSql.executeQuery(statement);
+
+                       if (rs.next())
+                       {
+                               return VizonDrawing.fromResultSet(rs);
+                       }
+
+                       return null;
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Unable to get next drawing in Vizon Database", ex);
+                       return null;
+               }
+       }
+
+       /**
+        * Finds the drawing closest to the specified date.
+        *
+        * @param date Date to look up.
+        *
+        * @return {@link VizonDrawing} or null if none can be found.
+        */
+       public VizonDrawing getDrawingByDate(LocalDateTime date)
+       {
+               if (date == null)
+               {
+                       return null;
+               }
+
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("SELECT * FROM vizon_drawings "
+                                       + "ORDER BY ABS(TIMESTAMPDIFF(second, drawing_date, ?) "
+                                       + "LIMIT 1");
+
+                       statement.setTimestamp(1, Timestamp.valueOf(date));
+
+                       ResultSet rs = vizonSql.executeQuery(statement);
+
+                       if (rs.next())
+                       {
+                               return VizonDrawing.fromResultSet(rs);
+                       }
+
+                       return null;
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Unable to get next drawing in Vizon Database", ex);
+                       return null;
+               }
+       }
+
+       public VizonDrawing getLatestDrawing()
+       {
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("SELECT * FROM vizon_drawings "
+                                       + "ORDER BY id DESC "
+                                       + "LIMIT 1");
+
+                       ResultSet rs = vizonSql.executeQuery(statement);
+
+                       if (rs.next())
+                       {
+                               return VizonDrawing.fromResultSet(rs);
+                       }
+
+                       return null;
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Unable to get next drawing in Vizon Database", ex);
+                       return null;
+               }
+       }
+
+       /**
+        * Gets the next drawing that's scheduled to take place.
+        *
+        * @return {@link VizonDrawing} or null if nothing is scheduled.
+        */
+       public VizonDrawing getNextDrawing()
+       {
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("SELECT * FROM vizon_drawings "
+                                       + "WHERE drawing_date >= NOW() "
+                                       + "ORDER BY drawing_date DESC "
+                                       + "LIMIT 1");
+
+                       ResultSet rs = vizonSql.executeQuery(statement);
+
+                       if (rs.next())
+                       {
+                               return VizonDrawing.fromResultSet(rs);
+                       }
+
+                       return null;
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Unable to get next drawing in Vizon Database", ex);
+                       return null;
+               }
+       }
+
+       public VizonUser findUserById(int id)
+       {
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("SELECT * FROM vizon_users "
+                                       + "WHERE id = ?");
+
+                       statement.setInt(1, id);
+
+                       ResultSet rs = vizonSql.executeQuery(statement);
+
+                       if (rs.next())
+                       {
+                               return VizonUser.fromResultSet(rs);
+                       }
+
+                       return null;
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Unable to find user by id in Vizon Database", ex);
+                       return null;
+               }
+       }
+
+       public VizonUser findUser(String nick)
+       {
+               if (nick == null)
+               {
+                       return null;
+               }
+
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("SELECT * FROM vizon_users WHERE nick = ?");
+                       statement.setString(1, nick);
+
+                       ResultSet rs = vizonSql.executeQuery(statement);
+
+                       if (rs.next())
+                       {
+                               return VizonUser.fromResultSet(rs);
+                       }
+
+                       return null;
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Unable to select or create user in Vizon Database", ex);
+                       return null;
+               }
+       }
+
+       /**
+        * Finds or creates a user. A user's id and nick are both unique in the
+        * database.
+        *
+        * @param nick Nick of the user. (Case insensitive)
+        *
+        * @return {@link VizonUser} or null if an error occurred.
+        */
+       public VizonUser findOrCreateUser(String nick)
+       {
+               if (nick == null)
+               {
+                       return null;
+               }
+
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("SELECT * FROM vizon_users WHERE nick = ?");
+                       statement.setString(1, nick);
+
+                       ResultSet rs = vizonSql.executeQuery(statement);
+
+                       if (rs.next())
+                       {
+                               return VizonUser.fromResultSet(rs);
+                       }
+
+                       statement = vizonSql.prepare("INSERT INTO vizon_users (nick) VALUES(?)");
+                       statement.setString(1, nick);
+
+                       int result = vizonSql.executeUpdateBlocking(statement);
+
+                       if (result != 1)
+                       {
+                               // Unable to insert new user.
+                               return null;
+                       }
+
+                       statement = vizonSql.prepare("SELECT * FROM vizon_users WHERE nick = ?");
+                       statement.setString(1, nick);
+
+                       rs = vizonSql.executeQuery(statement);
+
+                       if (rs.next())
+                       {
+                               return VizonUser.fromResultSet(rs);
+                       }
+
+                       return null;
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Unable to select or create user in Vizon Database", ex);
+                       return null;
+               }
+       }
+
+       public VizonRequest findVhostRequestByUserId(int userId)
+       {
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("SELECT vizon_requests.*, vizon_users.nick FROM vizon_requests "
+                                       + "INNER JOIN vizon_users "
+                                       + "ON vizon_users.id = vizon_requests.user_id "
+                                       + "WHERE user_id = ? "
+                                       + "AND status = ?");
+                       statement.setInt(1, userId);
+                       statement.setInt(2, RequestStatus.PENDING);
+
+                       ResultSet rs = vizonSql.executeQuery(statement);
+
+                       if (rs.next())
+                       {
+                               return VizonRequest.fromResultSet(rs);
+                       }
+
+                       return null;
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Unable to find vhost request for nick", ex);
+                       return null;
+               }
+       }
+
+       public VizonRequest findVhostRequest(String nick)
+       {
+               if (nick == null)
+               {
+                       return null;
+               }
+
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("SELECT vizon_requests.*, vizon_users.nick FROM vizon_requests"
+                                       + "INNER JOIN vizon_users "
+                                       + "ON vizon_users.id = vizon_requests.user_id"
+                                       + "WHERE vizon_users.nick = ?");
+                       statement.setString(1, nick);
+
+                       ResultSet rs = vizonSql.executeQuery(statement);
+
+                       if (rs.next())
+                       {
+                               return VizonRequest.fromResultSet(rs);
+                       }
+
+                       return null;
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Unable to find vhost request for nick", ex);
+                       return null;
+               }
+       }
+
+       public Collection<VizonRequest> findPendingVhostRequests()
+       {
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("SELECT vizon_requests.*, vizon_users.nick FROM vizon_requests "
+                                       + "INNER JOIN vizon_users "
+                                       + "ON vizon_users.id = vizon_requests.user_id "
+                                       + "WHERE vizon_requests.status = ?");
+                       statement.setInt(1, RequestStatus.PENDING);
+
+                       ResultSet rs = vizonSql.executeQuery(statement);
+
+                       List<VizonRequest> requests = new ArrayList<>();
+
+                       while (rs.next())
+                       {
+                               VizonRequest request = VizonRequest.fromResultSet(rs);
+
+                               if (request != null)
+                               {
+                                       requests.add(request);
+                               }
+                       }
+
+                       return requests;
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Unable to find vhost request for nick", ex);
+                       return null;
+               }
+       }
+
+       public boolean insertRequest(VizonRequest request)
+       {
+               if (request == null)
+               {
+                       return false;
+               }
+
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("INSERT INTO vizon_requests "
+                                       + "(user_id, vhost, status, reason, oper) "
+                                       + "VALUES (?, ?, ?, ?, ?)");
+                       statement.setInt(1, request.getUserId());
+                       statement.setString(2, request.getVhost());
+                       statement.setInt(3, request.getStatus());
+                       statement.setString(4, request.getReason());
+                       statement.setString(5, request.getOper());
+
+                       int result = vizonSql.executeUpdateBlocking(statement);
+
+                       return result > 0;
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Unable to insert vhost request", ex);
+                       return false;
+               }
+       }
+
+       public boolean updateRequest(VizonRequest request)
+       {
+               if (request == null)
+               {
+                       return false;
+               }
+
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("UPDATE vizon_requests "
+                                       + "SET vhost = ?, "
+                                       + "status = ?, "
+                                       + "reason = ?, "
+                                       + "oper = ? "
+                                       + "WHERE id = ?");
+                       statement.setString(1, request.getVhost());
+                       statement.setInt(2, request.getStatus());
+                       statement.setString(3, request.getReason());
+                       statement.setString(4, request.getOper());
+                       statement.setInt(5, request.getId());
+
+                       int result = vizonSql.executeUpdateBlocking(statement);
+
+                       return true;
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Unable to insert vhost request", ex);
+                       return false;
+               }
+       }
+
+       public boolean updateUser(VizonUser user)
+       {
+               if (user == null)
+               {
+                       return false;
+               }
+
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("UPDATE vizon_users "
+                                       + "SET vhost = ?, "
+                                       + "eligible = ?, "
+                                       + "bold = ?, "
+                                       + "obtained = ?, "
+                                       + "obtained_id = ?, "
+                                       + "multiplier = ?, "
+                                       + "jackpot = ?, "
+                                       + "permanent = ?, "
+                                       + "days = ? "
+                                       + "WHERE id = ?");
+
+                       statement.setString(1, user.getVhost());
+                       statement.setBoolean(2, user.isEligible());
+                       statement.setBoolean(3, user.isBold());
+                       statement.setTimestamp(4, Timestamp.valueOf(user.getObtained()));
+                       statement.setInt(5, user.getObtainedId());
+                       statement.setInt(6, user.getMultiplier());
+                       statement.setBoolean(7, user.isJackpot());
+                       statement.setBoolean(8, user.isPermanent());
+                       statement.setInt(9, user.getDays());
+                       statement.setInt(10, user.getId());
+
+                       int updated = vizonSql.executeUpdateBlocking(statement);
+
+                       return updated > 0;
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Unable to select bet for user and drawing in Vizon Database", ex);
+                       return false;
+               }
+       }
+
+       public boolean updateDrawing(VizonDrawing drawing)
+       {
+               if (drawing == null)
+               {
+                       return false;
+               }
+
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("UPDATE vizon_drawings "
+                                       + "SET first = ?, "
+                                       + "second = ?, "
+                                       + "third = ?, "
+                                       + "fourth = ?, "
+                                       + "fifth = ?, "
+                                       + "sixth = ? "
+                                       + "WHERE id = ?");
+
+                       statement.setInt(1, drawing.getDraws().get(0));
+                       statement.setInt(2, drawing.getDraws().get(1));
+                       statement.setInt(3, drawing.getDraws().get(2));
+                       statement.setInt(4, drawing.getDraws().get(3));
+                       statement.setInt(5, drawing.getDraws().get(4));
+                       statement.setInt(6, drawing.getDraws().get(5));
+                       statement.setInt(7, drawing.getId());
+
+                       int updated = vizonSql.executeUpdateBlocking(statement);
+
+                       return updated > 0;
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Unable to select bet for user and drawing in Vizon Database", ex);
+                       return false;
+               }
+       }
+
+       /**
+        * Attempts to find a {@link VizonBet} of the user for the specified
+        * drawing.
+        *
+        * @param user    User to find for.
+        * @param drawing Drawing to find for.
+        *
+        * @return {@link VizonBet} or null if none can be found.
+        */
+       public VizonBet findBetForUserAndDrawing(VizonUser user, VizonDrawing drawing)
+       {
+               if (user == null || drawing == null)
+               {
+                       return null;
+               }
+
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("SELECT * FROM vizon_bets WHERE vizon_users_id = ? AND vizon_drawings_id = ?");
+                       statement.setInt(1, user.getId());
+                       statement.setInt(2, drawing.getId());
+
+                       ResultSet rs = vizonSql.executeQuery(statement);
+
+                       if (rs.next())
+                       {
+                               return VizonBet.fromResultSet(rs);
+                       }
+
+                       return null;
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Unable to select bet for user and drawing in Vizon Database", ex);
+                       return null;
+               }
+       }
+
+       public List<VizonBet> findBetsForUser(VizonUser user)
+       {
+               return null;
+       }
+
+       public List<VizonBet> findBetsForDrawing(VizonDrawing drawing)
+       {
+               List<VizonBet> bets = new ArrayList<>();
+
+               if (drawing == null)
+               {
+                       return bets;
+               }
+
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("SELECT * FROM vizon_bets WHERE vizon_drawings_id = ?");
+                       statement.setInt(1, drawing.getId());
+
+                       ResultSet rs = vizonSql.executeQuery(statement);
+
+                       while (rs.next())
+                       {
+                               VizonBet bet = VizonBet.fromResultSet(rs);
+
+                               if (bet == null)
+                               {
+                                       continue;
+                               }
+
+                               bets.add(bet);
+                       }
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Unable to select bets for drawing in Vizon Database", ex);
+               }
+
+               return bets;
+       }
+
+       /**
+        * Creates a new bet for the user for the specified drawing.
+        *
+        * @param user    User to place the bet for.
+        * @param drawing Drawing to place the bet in.
+        * @param bet     Bet to place.
+        *
+        * @return True if successful, false otherwise.
+        */
+       public boolean createBetForUser(VizonUser user, VizonDrawing drawing, Bet bet)
+       {
+               if (user == null || drawing == null || bet == null)
+               {
+                       return false;
+               }
+
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("INSERT INTO vizon_bets "
+                                       + "(vizon_users_id, vizon_drawings_id, first, second, third, fourth, fifth, sixth) "
+                                       + "VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
+
+                       statement.setInt(1, user.getId());
+                       statement.setInt(2, drawing.getId());
+                       statement.setInt(3, bet.getFirst());
+                       statement.setInt(4, bet.getSecond());
+                       statement.setInt(5, bet.getThird());
+                       statement.setInt(6, bet.getFourth());
+                       statement.setInt(7, bet.getFifth());
+                       statement.setInt(8, bet.getSixth());
+
+                       int inserted = vizonSql.executeUpdateBlocking(statement);
+
+                       return inserted > 0;
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Error while inserting bet for user in Vizon Database", ex);
+                       return false;
+               }
+       }
+
+       /**
+        * Finds a scheduled drawing that has not been run yet (i.e. First == null)
+        *
+        * @return {@link VizonDrawing} or null if no unrun drawings exist
+        */
+       public VizonDrawing findEarliestUnrunDrawing()
+       {
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("SELECT * FROM vizon_drawings "
+                                       + "WHERE first IS NULL "
+                                       + "ORDER BY drawing_date "
+                                       + "LIMIT 1");
+
+                       ResultSet rs = vizonSql.executeQuery(statement);
+
+                       if (!rs.next())
+                       {
+                               return null;
+                       }
+
+                       return VizonDrawing.fromResultSet(rs);
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Error while trying to find earliest drawing in Vizon Database", ex);
+                       return null;
+               }
+       }
+
+       public VizonDrawing createDrawing(LocalDateTime date)
+       {
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("INSERT INTO vizon_drawings "
+                                       + "(drawing_date) "
+                                       + "VALUES(?)",
+                                       Statement.RETURN_GENERATED_KEYS);
+
+                       statement.setTimestamp(1, Timestamp.valueOf(date));
+
+                       int inserted = vizonSql.executeUpdateBlocking(statement);
+
+                       ResultSet rs = statement.getGeneratedKeys();
+
+                       if (rs == null || !rs.next())
+                       {
+                               return null;
+                       }
+
+                       int id = rs.getInt(1);
+
+                       statement = vizonSql.prepare("SELECT * FROM vizon_drawings "
+                                       + "WHERE id = ?");
+
+                       statement.setInt(1, id);
+
+                       rs = vizonSql.executeQuery(statement);
+
+                       if (rs == null || !rs.next())
+                       {
+                               return null;
+                       }
+
+                       return VizonDrawing.fromResultSet(rs);
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Error while trying to create drawing in Vizon Database", ex);
+                       return null;
+               }
+       }
+
+       public List<VizonUser> findAllUsers()
+       {
+               List<VizonUser> users = new ArrayList<>();
+
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("SELECT * FROM vizon_users");
+
+                       ResultSet rs = vizonSql.executeQuery(statement);
+
+                       while (rs.next())
+                       {
+                               VizonUser user = VizonUser.fromResultSet(rs);
+
+                               if (user == null)
+                               {
+                                       continue;
+                               }
+
+                               users.add(user);
+                       }
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Error while trying to get all users in Vizon Database", ex);
+               }
+
+               return users;
+       }
+
+       public int expireVhosts()
+       {
+               try
+               {
+                       PreparedStatement statement = vizonSql.prepare("UPDATE vizon_users "
+                                       + "SET vhost = null, "
+                                       + "eligible = 0, "
+                                       + "obtained = null, "
+                                       + "obtained_id = -1, "
+                                       + "multiplier = 0, "
+                                       + "bold = 0 "
+                                       + "WHERE permament = 0 "
+                                       + "AND DATE_ADD(obtained, INTERVAL days DAY) < NOW()");
+
+                       return vizonSql.executeUpdateBlocking(statement);
+
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Error in SQL statement to expire vhosts", ex);
+                       return 0;
+               }
+       }
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/db/VizonDrawing.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/db/VizonDrawing.java
new file mode 100644 (file)
index 0000000..ce26a3b
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon.db;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.List;
+import net.rizon.acid.plugins.vizon.Bet;
+import net.rizon.acid.plugins.vizon.DrawingState;
+import net.rizon.acid.plugins.vizon.Vizon;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class VizonDrawing
+{
+       private static final Logger logger = LoggerFactory.getLogger(VizonDrawing.class);
+
+       private final int id;
+       private final List<Integer> draws;
+       private final LocalDateTime date;
+
+       /**
+        * Constructs a new {@link VizonDrawing} from the supplied
+        * {@link ResultSet}.
+        *
+        * @param rs {@link ResultSet} to construct object from.
+        *
+        * @return {@link VizonDrawing} or null if unable to construct object.
+        */
+       public static VizonDrawing fromResultSet(ResultSet rs)
+       {
+               try
+               {
+                       // Grab id
+                       int id = rs.getInt("id");
+
+                       // Grab drawn numbers, ints default to 0 when they are NULL in
+                       // the database, and we cannot bet 0, so this is okay for now.
+                       // Should this change in the future, use rs.wasNull().
+                       int first = rs.getInt("first");
+                       int second = rs.getInt("second");
+                       int third = rs.getInt("third");
+                       int fourth = rs.getInt("fourth");
+                       int fifth = rs.getInt("fifth");
+                       int sixth = rs.getInt("sixth");
+
+                       // Grab date this drawing is supposed to take place, or took place
+                       // depending on what time it is now.
+                       Timestamp ts = rs.getTimestamp("drawing_date");
+                       List<Integer> draws = Arrays.asList(
+                                       first,
+                                       second,
+                                       third,
+                                       fourth,
+                                       fifth,
+                                       sixth);
+
+                       return new VizonDrawing(id, draws, ts.toLocalDateTime());
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Unable to construct VizonDrawing object", ex);
+                       return null;
+               }
+       }
+
+       private VizonDrawing(int id, List<Integer> draws, LocalDateTime date)
+       {
+               this.id = id;
+               this.draws = draws;
+               this.date = date;
+       }
+
+       public int getId()
+       {
+               return id;
+       }
+
+       public List<Integer> getDraws()
+       {
+               return draws;
+       }
+
+       public LocalDateTime getDate()
+       {
+               return date;
+       }
+
+       public int checkCorrect(Bet bet)
+       {
+               return checkCorrect(bet.asList());
+       }
+
+       public int checkCorrect(List<Integer> bets)
+       {
+               int correct = 0;
+
+               // Check how many of the bets are in the draws.
+               for (int bet : bets)
+               {
+                       if (draws.contains(bet))
+                       {
+                               correct++;
+                       }
+               }
+
+               return correct;
+       }
+
+       public void setDrawingResult(Bet result)
+       {
+               this.draws.set(0, result.getFirst());
+               this.draws.set(1, result.getSecond());
+               this.draws.set(2, result.getThird());
+               this.draws.set(3, result.getFourth());
+               this.draws.set(4, result.getFifth());
+               this.draws.set(5, result.getSixth());
+       }
+
+       public DrawingState getState()
+       {
+               LocalDateTime now = LocalDateTime.now();
+
+               if (getOpenDate().isAfter(now))
+               {
+                       return DrawingState.PREPARING;
+               }
+               else if (getCloseDate().isAfter(now))
+               {
+                       return DrawingState.OPEN;
+               }
+               else
+               {
+                       return DrawingState.CLOSED;
+               }
+       }
+
+       public LocalDateTime getOpenDate()
+       {
+               Duration before = Vizon.getConf().getOpenTimeInterval();
+
+               return this.date.minus(before);
+       }
+
+       public LocalDateTime getCloseDate()
+       {
+               Duration before = Vizon.getConf().getCloseTimeInterval();
+
+               return this.date.minus(before);
+       }
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/db/VizonRequest.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/db/VizonRequest.java
new file mode 100644 (file)
index 0000000..59df494
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon.db;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.LocalDateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class VizonRequest
+{
+       private static final Logger logger = LoggerFactory.getLogger(VizonRequest.class);
+
+       private final int id;
+       private final int userId;
+       private final String nick;
+       private String vhost;
+       private int status;
+       private String reason;
+       private String oper;
+       private LocalDateTime date;
+
+       public static VizonRequest fromResultSet(ResultSet rs)
+       {
+               try
+               {
+                       int id = rs.getInt("id");
+                       int userId = rs.getInt("user_id");
+                       String vhost = rs.getString("vhost");
+                       String nick = rs.getString("nick");
+                       int status = rs.getInt("status");
+                       String reason = rs.getString("reason");
+                       String oper = rs.getString("oper");
+                       LocalDateTime date = rs.getTimestamp("date").toLocalDateTime();
+
+                       return new VizonRequest(id, userId, nick, vhost, status, reason, oper, date);
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Unable to construct VizonRequest from ResultSet", ex);
+                       return null;
+               }
+       }
+
+       public VizonRequest(int id, int userId, String nick, String vhost, int status, String reason, String oper, LocalDateTime date)
+       {
+               this.id = id;
+               this.userId = userId;
+               this.nick = nick;
+               this.vhost = vhost;
+               this.status = status;
+               this.reason = reason;
+               this.oper = oper;
+               this.date = date;
+       }
+
+       public int getId()
+       {
+               return id;
+       }
+
+       public int getUserId()
+       {
+               return userId;
+       }
+
+       public String getNick()
+       {
+               return nick;
+       }
+
+       public String getVhost()
+       {
+               return vhost;
+       }
+
+       public int getStatus()
+       {
+               return status;
+       }
+
+       public String getReason()
+       {
+               return reason;
+       }
+
+       public String getOper()
+       {
+               return oper;
+       }
+
+       public LocalDateTime getDate()
+       {
+               return date;
+       }
+
+       public void setStatus(int status)
+       {
+               this.status = status;
+       }
+
+       public void setReason(String reason)
+       {
+               this.reason = reason;
+       }
+
+       public void setOper(String oper)
+       {
+               this.oper = oper;
+       }
+
+       public void setDate(LocalDateTime date)
+       {
+               this.date = date;
+       }
+
+       public void setVhost(String vhost)
+       {
+               this.vhost = vhost;
+       }
+
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/db/VizonUser.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/db/VizonUser.java
new file mode 100644 (file)
index 0000000..aa8a7f7
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon.db;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class VizonUser
+{
+       private static final Logger logger = LoggerFactory.getLogger(VizonUser.class);
+
+       private final int id;
+       private final String nick;
+       private String vhost;
+       private boolean eligible;
+       private boolean bold;
+       private LocalDateTime obtained;
+       private int obtainedId;
+       private int expires;
+       private int multiplier;
+       private boolean jackpot;
+       private boolean permanent;
+       private int days;
+
+       public static VizonUser fromResultSet(ResultSet rs)
+       {
+               try
+               {
+                       int id = rs.getInt("id");
+                       String nick = rs.getString("nick");
+                       String vhost = rs.getString("vhost");
+                       boolean eligible = rs.getBoolean("eligible");
+                       boolean bold = rs.getBoolean("bold");
+                       int expires = rs.getInt("expires");
+                       Timestamp obtained = rs.getTimestamp("obtained");
+                       int obtainedId = rs.getInt("obtained_id");
+                       int multiplier = rs.getInt("multiplier");
+                       boolean jackpot = rs.getBoolean("jackpot");
+                       boolean permanent = rs.getBoolean("permanent");
+                       int days = rs.getInt("days");
+
+                       LocalDateTime date = null;
+
+                       if (obtained != null)
+                       {
+                               date = obtained.toLocalDateTime();
+                       }
+
+                       return new VizonUser(
+                                       id,
+                                       nick,
+                                       vhost,
+                                       eligible,
+                                       bold,
+                                       date,
+                                       obtainedId,
+                                       expires,
+                                       multiplier,
+                                       jackpot,
+                                       permanent,
+                                       days);
+               }
+               catch (SQLException ex)
+               {
+                       logger.warn("Unable to construct VizonUser from ResultSet", ex);
+                       return null;
+               }
+       }
+
+
+       private VizonUser(
+                       int id,
+                       String nick,
+                       String vhost,
+                       boolean eligible,
+                       boolean bold,
+                       LocalDateTime obtained,
+                       int obtainedId,
+                       int expires,
+                       int multiplier,
+                       boolean jackpot,
+                       boolean permanent,
+                       int days)
+       {
+               this.id = id;
+               this.nick = nick;
+               this.vhost = vhost;
+               this.eligible = eligible;
+               this.bold = bold;
+               this.obtained = obtained;
+               this.obtainedId = obtainedId;
+               this.expires = expires;
+               this.multiplier = multiplier;
+               this.jackpot = jackpot;
+               this.permanent = permanent;
+               this.days = days;
+       }
+
+       public int getId()
+       {
+               return id;
+       }
+
+       public String getNick()
+       {
+               return nick;
+       }
+
+       public String getVhost()
+       {
+               return vhost;
+       }
+
+       public boolean isEligible()
+       {
+               return eligible;
+       }
+
+       public boolean isBold()
+       {
+               return bold;
+       }
+
+       public LocalDateTime getObtained()
+       {
+               return obtained;
+       }
+
+       public int getExpires()
+       {
+               return expires;
+       }
+
+       public boolean isJackpot()
+       {
+               return jackpot;
+       }
+
+       public boolean isPermanent()
+       {
+               return permanent;
+       }
+
+       public int getDays()
+       {
+               return days;
+       }
+
+       public void addDays(int days)
+       {
+               this.days += days;
+       }
+
+       public int getMultiplier()
+       {
+               return multiplier;
+       }
+
+       public void incrementMultiplier()
+       {
+               multiplier++;
+       }
+
+       public void resetMultiplier()
+       {
+               multiplier = 0;
+       }
+
+       public void setBold(boolean bold)
+       {
+               this.bold = bold;
+       }
+
+       public void setVhost(String vhost)
+       {
+               this.vhost = vhost;
+       }
+
+       public void setEligible(boolean eligible)
+       {
+               this.eligible = eligible;
+       }
+
+       public void setObtained(LocalDateTime obtained)
+       {
+               this.obtained = obtained;
+       }
+
+       public void setExpires(int expires)
+       {
+               this.expires = expires;
+       }
+
+       public void setJackpot(boolean jackpot)
+       {
+               this.jackpot = jackpot;
+       }
+
+       public void setPermanent(boolean permanent)
+       {
+               this.permanent = permanent;
+       }
+
+       public int getObtainedId()
+       {
+               return this.obtainedId;
+       }
+
+       public void setObtainedId(int drawingId)
+       {
+               this.obtainedId = drawingId;
+       }
+
+}
diff --git a/vizon/src/main/java/net/rizon/acid/plugins/vizon/util/VizonTemporal.java b/vizon/src/main/java/net/rizon/acid/plugins/vizon/util/VizonTemporal.java
new file mode 100644 (file)
index 0000000..2886739
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon.util;
+
+import java.time.DayOfWeek;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.temporal.TemporalAdjusters;
+import net.rizon.acid.plugins.vizon.conf.VizonConfig;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class VizonTemporal
+{
+       /**
+        * Determines the next drawing that should place, after the specified date.
+        *
+        * @param date   Date after which the next drawing should be determined.
+        * @param config Config file
+        *
+        * @return Next date
+        */
+       public static LocalDateTime determineNextDrawing(LocalDateTime date, VizonConfig config)
+       {
+               LocalTime drawingTime = LocalTime.parse(config.drawingTime);
+               LocalDateTime drawing = LocalDateTime.MAX;
+
+               /*
+                * This for loop constructs a LocalDateTime object for every day that is
+                * in the config and then checks if it is closer to now than the
+                * previous one it determined.
+                */
+               for (int dayOfWeek : config.days)
+               {
+                       LocalDateTime temp = date
+                                       .with(TemporalAdjusters.nextOrSame(DayOfWeek.of(dayOfWeek)))
+                                       .withHour(drawingTime.getHour())
+                                       .withMinute(drawingTime.getMinute())
+                                       .withSecond(drawingTime.getSecond());
+
+                       if (temp.isAfter(date) && temp.isBefore(drawing))
+                       {
+                               drawing = temp;
+                       }
+               }
+
+               return drawing;
+       }
+}
diff --git a/vizon/src/test/java/net/rizon/acid/plugins/vizon/BetTest.java b/vizon/src/test/java/net/rizon/acid/plugins/vizon/BetTest.java
new file mode 100644 (file)
index 0000000..ec9c322
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class BetTest
+{
+
+       public BetTest()
+       {
+       }
+
+       @Before
+       public void setUp()
+       {
+       }
+
+       /**
+        * Test of getFirst method, of class Bet.
+        */
+       @Test
+       public void hasSameNumbersTest()
+       {
+               Bet bet = new Bet(1, 2, 3, 4, 5, 6);
+               Bet draw = new Bet(1, 2, 3, 4, 5, 6);
+
+               boolean areEqual = bet.hasSameNumbers(draw);
+
+               Assert.assertTrue(areEqual);
+       }
+}
diff --git a/vizon/src/test/java/net/rizon/acid/plugins/vizon/BetValidatorTest.java b/vizon/src/test/java/net/rizon/acid/plugins/vizon/BetValidatorTest.java
new file mode 100644 (file)
index 0000000..05a2cd4
--- /dev/null
@@ -0,0 +1,309 @@
+/*
+ * Copyright (c) 2016, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon;
+
+import java.util.Optional;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Unit tests for the bet validator.
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class BetValidatorTest
+{
+       /**
+        * Test whether or not the validate method returns an empty optional when
+        * you present null to it.
+        */
+       @Test
+       public void validateNullTest()
+       {
+               String bet = null;
+
+               Optional<Bet> result = BetValidator.validate(bet);
+
+               Assert.assertFalse(result.isPresent());
+       }
+
+       /**
+        * Test whether or not the validate method returns an empty optional when
+        * you present an empty to it.
+        */
+       @Test
+       public void validateEmptyStringTest()
+       {
+               String bet = "";
+
+               Optional<Bet> result = BetValidator.validate(bet);
+
+               Assert.assertFalse(result.isPresent());
+       }
+
+       /**
+        * Test whether or not the validate method returns an empty optional when
+        * you present an incorrect string to it.
+        */
+       @Test
+       public void validateNotEnoughNumbersTest()
+       {
+               String bet = "1 2 3";
+
+               Optional<Bet> result = BetValidator.validate(bet);
+
+               Assert.assertFalse(result.isPresent());
+       }
+
+       /**
+        * Test whether or not the validate method returns an empty optional when
+        * you present an incorrect string to it.
+        */
+       @Test
+       public void validateTooManyNumbersTest()
+       {
+               String bet = "1 2 3 4 5 6 7";
+
+               Optional<Bet> result = BetValidator.validate(bet);
+
+               Assert.assertFalse(result.isPresent());
+       }
+
+       /**
+        * Test whether or not the validate method returns an empty optional when
+        * you present an incorrect string to it.
+        */
+       @Test
+       public void validateDoubleNumbersTest()
+       {
+               String bet = "1 2 1 3 4 5";
+
+               Optional<Bet> result = BetValidator.validate(bet);
+
+               Assert.assertFalse(result.isPresent());
+       }
+
+       /**
+        * Test whether or not the validate method returns an empty optional when
+        * you present an incorrect string to it.
+        */
+       @Test
+       public void validateNegativeNumbersTest()
+       {
+               String bet = "1 2 -11 3 4 5";
+
+               Optional<Bet> result = BetValidator.validate(bet);
+
+               Assert.assertFalse(result.isPresent());
+       }
+
+       /**
+        * Test whether or not the validate method returns an empty optional when
+        * you present an incorrect string to it.
+        */
+       @Test
+       public void validateOutLowerBoundTest()
+       {
+               String bet = "1 2 0 3 4 5";
+
+               Optional<Bet> result = BetValidator.validate(bet);
+
+               Assert.assertFalse(result.isPresent());
+       }
+
+       /**
+        * Test whether or not the validate method returns an empty optional when
+        * you present an incorrect string to it.
+        */
+       @Test
+       public void validateOutUpperBoundTest()
+       {
+               String bet = "1 2 30 3 4 5";
+
+               Optional<Bet> result = BetValidator.validate(bet);
+
+               Assert.assertFalse(result.isPresent());
+       }
+
+       /**
+        * Test whether or not the validate method returns an filled optional when
+        * you present a correct string to it.
+        */
+       @Test
+       public void validateOnLowerBoundTest()
+       {
+               String bet = "1 2 3 4 5 6";
+
+               Optional<Bet> result = BetValidator.validate(bet);
+
+               Assert.assertTrue(result.isPresent());
+               Assert.assertEquals(1, result.get().getFirst());
+               Assert.assertEquals(2, result.get().getSecond());
+               Assert.assertEquals(3, result.get().getThird());
+               Assert.assertEquals(4, result.get().getFourth());
+               Assert.assertEquals(5, result.get().getFifth());
+               Assert.assertEquals(6, result.get().getSixth());
+       }
+
+       /**
+        * Test whether or not the validate method returns an filled optional when
+        * you present a correct string to it.
+        */
+       @Test
+       public void validateOnUpperBoundTest()
+       {
+               String bet = "1 2 3 4 5 29";
+
+               Optional<Bet> result = BetValidator.validate(bet);
+
+               Assert.assertTrue(result.isPresent());
+               Assert.assertEquals(1, result.get().getFirst());
+               Assert.assertEquals(2, result.get().getSecond());
+               Assert.assertEquals(3, result.get().getThird());
+               Assert.assertEquals(4, result.get().getFourth());
+               Assert.assertEquals(5, result.get().getFifth());
+               Assert.assertEquals(29, result.get().getSixth());
+       }
+
+       /**
+        * Test whether or not the validate method returns an filled optional when
+        * you present a correct string to it.
+        */
+       @Test
+       public void validateResultRemainsUnsortedTest()
+       {
+               String bet = "9 2 11 4 21 29";
+
+               Optional<Bet> result = BetValidator.validate(bet);
+
+               Assert.assertTrue(result.isPresent());
+               Assert.assertEquals(9, result.get().getFirst());
+               Assert.assertEquals(2, result.get().getSecond());
+               Assert.assertEquals(11, result.get().getThird());
+               Assert.assertEquals(4, result.get().getFourth());
+               Assert.assertEquals(21, result.get().getFifth());
+               Assert.assertEquals(29, result.get().getSixth());
+       }
+
+       /**
+        * Test whether or not the validator does not mind multiple spaces in front.
+        * <p>
+        * Reasoning: Users can accidentally type a space and it's something that
+        * can be solved easy with a removeEmptyEntries call in standard java
+        * library.
+        */
+       @Test
+       public void validateAccidentalSpaceFrontTest()
+       {
+               String bet = "   9 2 11 4 21 29";
+
+               Optional<Bet> result = BetValidator.validate(bet);
+
+               Assert.assertTrue(result.isPresent());
+               Assert.assertEquals(9, result.get().getFirst());
+               Assert.assertEquals(2, result.get().getSecond());
+               Assert.assertEquals(11, result.get().getThird());
+               Assert.assertEquals(4, result.get().getFourth());
+               Assert.assertEquals(21, result.get().getFifth());
+               Assert.assertEquals(29, result.get().getSixth());
+       }
+
+       /**
+        * Test whether or not the validator does not mind multiple spaces at the
+        * end.
+        * <p>
+        * Reasoning: Users can accidentally type a space and it's something that
+        * can be solved easy with a removeEmptyEntries call in standard java
+        * library.
+        */
+       @Test
+       public void validateAccidentalSpaceEndTest()
+       {
+               String bet = "9 2 11 4 21 29   ";
+
+               Optional<Bet> result = BetValidator.validate(bet);
+
+               Assert.assertTrue(result.isPresent());
+               Assert.assertEquals(9, result.get().getFirst());
+               Assert.assertEquals(2, result.get().getSecond());
+               Assert.assertEquals(11, result.get().getThird());
+               Assert.assertEquals(4, result.get().getFourth());
+               Assert.assertEquals(21, result.get().getFifth());
+               Assert.assertEquals(29, result.get().getSixth());
+       }
+
+       /**
+        * Test whether or not the validator does not mind multiple spaces in
+        * between.
+        * <p>
+        * Reasoning: Users can accidentally type a space and it's something that
+        * can be solved easy with a removeEmptyEntries call in standard java
+        * library.
+        */
+       @Test
+       public void validateAccidentalSpaceBetweenTest()
+       {
+               String bet = "9   2 11 4    21  29";
+
+               Optional<Bet> result = BetValidator.validate(bet);
+
+               Assert.assertTrue(result.isPresent());
+               Assert.assertEquals(9, result.get().getFirst());
+               Assert.assertEquals(2, result.get().getSecond());
+               Assert.assertEquals(11, result.get().getThird());
+               Assert.assertEquals(4, result.get().getFourth());
+               Assert.assertEquals(21, result.get().getFifth());
+               Assert.assertEquals(29, result.get().getSixth());
+       }
+
+       /**
+        * Test whether or not the validator returns an empty Optional when
+        * presented with gibberish.
+        */
+       @Test
+       public void validateNoNumbersTest()
+       {
+               String bet = "hello world!";
+
+               Optional<Bet> result = BetValidator.validate(bet);
+
+               Assert.assertFalse(result.isPresent());
+       }
+
+       /**
+        * Test whether or not the validator returns an empty Optional when
+        * presented with gibberish.
+        */
+       @Test
+       public void validateTextAfterNumbersTest()
+       {
+               String bet = "1 2 3 4 5 6 hello world!";
+
+               Optional<Bet> result = BetValidator.validate(bet);
+
+               Assert.assertFalse(result.isPresent());
+       }
+}
diff --git a/vizon/src/test/java/net/rizon/acid/plugins/vizon/DrawGeneratorTest.java b/vizon/src/test/java/net/rizon/acid/plugins/vizon/DrawGeneratorTest.java
new file mode 100644 (file)
index 0000000..7e7d7c8
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+public class DrawGeneratorTest
+{
+       private DrawGenerator generator;
+
+       public DrawGeneratorTest()
+       {
+       }
+
+       @Before
+       public void setup()
+       {
+               generator = new DrawGenerator();
+       }
+
+       /**
+        * Test of generate method, of class DrawGenerator.
+        */
+       @Test
+       public void generateTest()
+       {
+               Bet bet = generator.generate();
+               Assert.assertNotNull(bet);
+       }
+
+       @Ignore("Testing randomness")
+       @Test
+       public void generate1000Test()
+       {
+               int[] draws = new int[29];
+
+               for (int i = 0; i < 10000; i++)
+               {
+                       Bet bet = generator.generate();
+
+                       draws[bet.getFirst() - 1]++;
+                       draws[bet.getSecond() - 1]++;
+                       draws[bet.getThird() - 1]++;
+                       draws[bet.getFourth() - 1]++;
+                       draws[bet.getFifth() - 1]++;
+                       draws[bet.getSixth() - 1]++;
+               }
+
+               for (int i = 0; i < 29; i++)
+               {
+                       int draw = draws[i];
+                       System.out.println(String.format("Number %2d was drawn %d times", i + 1, draw));
+               }
+       }
+
+       @Ignore("Testing randomness")
+       @Test
+       public void bet123456Test()
+       {
+               Bet bet = new Bet(1, 2, 3, 4, 5, 6);
+
+               long totalRuns = 0;
+               int cycles = 100;
+
+               Bet draw;
+
+               for (int i = 0; i < cycles; i++)
+               {
+                       int runs = 0;
+
+                       do
+                       {
+                               draw = generator.generate();
+                               runs++;
+                       }
+                       while (!draw.hasSameNumbers(bet));
+
+                       totalRuns += runs;
+
+                       System.out.println(String.format("Run %3d completed", i + 1));
+               }
+
+               System.out.println(String.format("It took an average of %d runs to win first prize with bet 1 2 3 4 5 6", totalRuns / cycles));
+       }
+
+       /**
+        * Test of generateSpecial method, of class DrawGenerator.
+        */
+       @Test
+       public void testGenerateSpecial()
+       {
+
+       }
+
+}
diff --git a/vizon/src/test/java/net/rizon/acid/plugins/vizon/VizonDrawingTest.java b/vizon/src/test/java/net/rizon/acid/plugins/vizon/VizonDrawingTest.java
new file mode 100644 (file)
index 0000000..1eb0851
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2017, orillion <orillion@rizon.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.rizon.acid.plugins.vizon;
+
+import net.rizon.acid.plugins.vizon.db.VizonDrawing;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import org.junit.Assert;
+import static org.junit.Assert.fail;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import static org.mockito.Mockito.when;
+import org.mockito.runners.MockitoJUnitRunner;
+
+/**
+ *
+ * @author orillion <orillion@rizon.net>
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class VizonDrawingTest
+{
+       @Mock
+       private ResultSet resultSet;
+
+       private VizonDrawing drawing;
+
+       public VizonDrawingTest()
+       {
+       }
+
+       @Before
+       public void setUp()
+       {
+               try
+               {
+                       when(resultSet.getInt("id")).thenReturn(1);
+                       when(resultSet.getInt("first")).thenReturn(1);
+                       when(resultSet.getInt("second")).thenReturn(2);
+                       when(resultSet.getInt("third")).thenReturn(3);
+                       when(resultSet.getInt("fourth")).thenReturn(4);
+                       when(resultSet.getInt("fifth")).thenReturn(5);
+                       when(resultSet.getInt("sixth")).thenReturn(6);
+                       when(resultSet.getTimestamp("drawing_date")).thenReturn(Timestamp.valueOf("2016-12-12 00:00:00"));
+               }
+               catch (SQLException ex)
+               {
+                       fail("SQLException");
+               }
+
+               drawing = VizonDrawing.fromResultSet(resultSet);
+       }
+
+       /**
+        * Tests if the calculator returns the correct amount of correct values.
+        */
+       @Test
+       public void zeroCorrectTest()
+       {
+               Bet bet = new Bet(11, 12, 13, 14, 15, 16);
+
+               int actual = drawing.checkCorrect(bet);
+               int expected = 0;
+
+               Assert.assertEquals(expected, actual);
+       }
+
+       /**
+        * Tests if the calculator returns the correct amount of correct values.
+        */
+       @Test
+       public void oneCorrectTest()
+       {
+               Bet bet = new Bet(1, 12, 13, 14, 15, 16);
+
+               int actual = drawing.checkCorrect(bet);
+               int expected = 1;
+
+               Assert.assertEquals(expected, actual);
+       }
+
+       /**
+        * Tests if the calculator returns the correct amount of correct values.
+        */
+       @Test
+       public void twoCorrectTest()
+       {
+               Bet bet = new Bet(1, 2, 13, 14, 15, 16);
+
+               int actual = drawing.checkCorrect(bet);
+               int expected = 2;
+
+               Assert.assertEquals(expected, actual);
+       }
+
+       /**
+        * Tests if the calculator returns the correct amount of correct values.
+        */
+       @Test
+       public void threeCorrectTest()
+       {
+               Bet bet = new Bet(1, 2, 3, 14, 15, 16);
+
+               int actual = drawing.checkCorrect(bet);
+               int expected = 3;
+
+               Assert.assertEquals(expected, actual);
+       }
+
+       /**
+        * Tests if the calculator returns the correct amount of correct values.
+        */
+       @Test
+       public void fourCorrectTest()
+       {
+               Bet bet = new Bet(1, 2, 3, 4, 15, 16);
+
+               int actual = drawing.checkCorrect(bet);
+               int expected = 4;
+
+               Assert.assertEquals(expected, actual);
+       }
+
+       /**
+        * Tests if the calculator returns the correct amount of correct values.
+        */
+       @Test
+       public void fiveCorrectTest()
+       {
+               Bet bet = new Bet(1, 2, 3, 4, 5, 16);
+
+               int actual = drawing.checkCorrect(bet);
+               int expected = 5;
+
+               Assert.assertEquals(expected, actual);
+       }
+
+       /**
+        * Tests if the calculator returns the correct amount of correct values.
+        */
+       @Test
+       public void sixCorrectTest()
+       {
+               Bet bet = new Bet(1, 2, 3, 4, 5, 6);
+
+               int actual = drawing.checkCorrect(bet);
+               int expected = 6;
+
+               Assert.assertEquals(expected, actual);
+       }
+}
diff --git a/vizon/vizon.example.yml b/vizon/vizon.example.yml
new file mode 100644 (file)
index 0000000..8e78c1a
--- /dev/null
@@ -0,0 +1,79 @@
+clients:
+ -
+  nick: VizonBot
+  user: colors
+  host: services.rizon.net
+  vhost: for.everyone
+  name: VIzon
+  modes: iUop
+  channels: [ vizonChannel ]
+  commands:
+   -
+    name: help
+    privilege: anyone
+    clazz: net.rizon.acid.commands.Help
+   -
+    name: bet
+    privilege: anyone
+    clazz: net.rizon.acid.plugins.vizon.commands.BetCommand
+   -
+    name: "!bet"
+    privilege: anyone
+    clazz: net.rizon.acid.plugins.vizon.commands.BetCommand
+   -
+    name: check
+    privilege: anyone
+    clazz: net.rizon.acid.plugins.vizon.commands.CheckCommand
+   -
+    name: "!check"
+    privilege: anyone
+    clazz: net.rizon.acid.plugins.vizon.commands.CheckCommand
+   -
+    name: request
+    privilege: anyone
+    clazz: net.rizon.acid.plugins.vizon.commands.RequestCommand
+   -
+    name: "!request"
+    privilege: anyone
+    clazz: net.rizon.acid.plugins.vizon.commands.RequestCommand
+   -
+    name: vip
+    privilege: anyone
+    clazz: net.rizon.acid.plugins.vizon.commands.VipCommand
+   -
+    name: "!vip"
+    privilege: anyone
+    clazz: net.rizon.acid.plugins.vizon.commands.VipCommand
+   -
+    name: pending
+    channels: [vhost]
+    privilege: none
+    clazz: net.rizon.acid.plugins.vizon.commands.PendingRequestCommand
+   -
+    name: approve
+    channels: [vhost]
+    privilege: none
+    clazz: net.rizon.acid.plugins.vizon.commands.ApproveRequestCommand
+   -
+    name: reject
+    channels: [vhost]
+    privilege: none
+    clazz: net.rizon.acid.plugins.vizon.commands.RejectRequestCommand
+   -
+    name: delete
+    channels: [vhost]
+    privilege: none
+    clazz: net.rizon.acid.plugins.vizon.commands.DeleteCommand
+
+vizonChannel: "#opers"
+vizonBot: "VizonBot"
+
+days: [3, 5, 7]
+drawingOpen: "+24h"
+drawingClose: "+10m"
+drawingTime: "22:00:00"
+
+firstPrize: 120
+secondPrize: 60
+thirdPrize: 30
+consolationPrize: 10
diff --git a/vizon/vizon.sql b/vizon/vizon.sql
new file mode 100644 (file)
index 0000000..c9ca4c0
--- /dev/null
@@ -0,0 +1,88 @@
+-- MySQL Script generated by MySQL Workbench
+-- 01/15/17 12:21:36
+-- Model: New Model    Version: 1.0
+-- MySQL Workbench Forward Engineering
+
+SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
+SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
+SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES';
+
+-- -----------------------------------------------------
+-- Schema vizon
+-- -----------------------------------------------------
+
+-- -----------------------------------------------------
+-- Schema vizon
+-- -----------------------------------------------------
+CREATE SCHEMA IF NOT EXISTS `vizon` DEFAULT CHARACTER SET latin1 ;
+USE `vizon` ;
+
+-- -----------------------------------------------------
+-- Table `vizon`.`vizon_users`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS `vizon`.`vizon_users` (
+  `id` INT NOT NULL AUTO_INCREMENT,
+  `nick` VARCHAR(64) NOT NULL,
+  `vhost` VARCHAR(64) NULL,
+  `eligible` TINYINT(1) NULL DEFAULT 0,
+  `bold` TINYINT(1) NULL DEFAULT 0,
+  `expires` INT NULL DEFAULT -1,
+  `obtained` TIMESTAMP NULL,
+  `multiplier` INT NULL DEFAULT 1,
+  `jackpot` TINYINT(1) NULL DEFAULT 0,
+  `permanent` TINYINT(1) NULL DEFAULT 0,
+  `days` INT NULL DEFAULT 0,
+  PRIMARY KEY (`id`),
+  UNIQUE INDEX `nick_UNIQUE` (`nick` ASC))
+ENGINE = InnoDB;
+
+
+-- -----------------------------------------------------
+-- Table `vizon`.`vizon_drawings`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS `vizon`.`vizon_drawings` (
+  `id` INT NOT NULL AUTO_INCREMENT,
+  `first` INT NULL,
+  `second` INT NULL,
+  `third` INT NULL,
+  `fourth` INT NULL,
+  `fifth` INT NULL,
+  `sixth` INT NULL,
+  `drawing_date` TIMESTAMP NOT NULL,
+  PRIMARY KEY (`id`))
+ENGINE = InnoDB;
+
+
+-- -----------------------------------------------------
+-- Table `vizon`.`vizon_bets`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS `vizon`.`vizon_bets` (
+  `id` INT NOT NULL AUTO_INCREMENT,
+  `vizon_users_id` INT NOT NULL,
+  `vizon_drawings_id` INT NOT NULL,
+  `first` INT NOT NULL,
+  `second` INT NOT NULL,
+  `third` INT NOT NULL,
+  `fourth` INT NOT NULL,
+  `fifth` INT NOT NULL,
+  `sixth` INT NOT NULL,
+  `placed` TIMESTAMP NOT NULL DEFAULT NOW(),
+  PRIMARY KEY (`id`),
+  INDEX `fk_vizon_bets_vizon_users_idx` (`vizon_users_id` ASC),
+  INDEX `fk_vizon_bets_vizon_drawings1_idx` (`vizon_drawings_id` ASC),
+  CONSTRAINT `fk_vizon_bets_vizon_users`
+    FOREIGN KEY (`vizon_users_id`)
+    REFERENCES `vizon`.`vizon_users` (`id`)
+    ON DELETE CASCADE
+    ON UPDATE CASCADE,
+  CONSTRAINT `fk_vizon_bets_vizon_drawings1`
+    FOREIGN KEY (`vizon_drawings_id`)
+    REFERENCES `vizon`.`vizon_drawings` (`id`)
+    ON DELETE CASCADE
+    ON UPDATE CASCADE)
+ENGINE = InnoDB;
+
+
+SET SQL_MODE=@OLD_SQL_MODE;
+SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
+SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;