Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New OutcomePicker based on other users and their prediction history #259

Merged
merged 30 commits into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
93d70f0
Add tag capability request to irc client
15sawyer Jul 4, 2022
8fdde33
IrcClient: capabilities and custom handlers.
15sawyer Jul 6, 2022
238233f
Fix KittehServerMessageTagException when parsing 'emote-sets'
15sawyer Jul 8, 2022
7e68b4d
Add new db tables and queries for prediction recording.
15sawyer Jul 8, 2022
2900f8d
Record predictions if enabled
15sawyer Jul 8, 2022
a22379c
Add sqlite tables.
15sawyer Jul 8, 2022
2f68653
Add mostTrusted outcome picker.
15sawyer Jul 9, 2022
e988323
Merge pull request #230 from RakSrinaNa/develop
Rakambda Jul 17, 2022
8f8c8f9
Merge pull request #247 from RakSrinaNa/develop
Rakambda Aug 6, 2022
55124fd
Fix sqlite multithread issue.
15sawyer Aug 25, 2022
6b1d5e7
Fork-sync merge
15sawyer Aug 26, 2022
45311ed
Tests added
15sawyer Aug 26, 2022
cffd64f
Add own placed predictions to db
15sawyer Aug 26, 2022
10a7288
Add fork info to readme
15sawyer Aug 26, 2022
c6ee003
Update README
15sawyer Aug 27, 2022
19f0896
Merge branch 'develop' into main
15sawyer Aug 27, 2022
f516997
Refactor a bunch
Rakambda Aug 27, 2022
b7a3ef2
Rename
Rakambda Aug 27, 2022
b7c492e
Test
Rakambda Aug 27, 2022
33dda69
Update README
15sawyer Aug 27, 2022
1ff67e4
Annots & stuff
Rakambda Aug 27, 2022
dba9555
Config test
Rakambda Aug 27, 2022
0c615ad
Change character set of prediction title and outcome.
15sawyer Aug 27, 2022
99e80b3
Merge branch 'main' of github.com:15sawyer/ChannelPointsMiner
15sawyer Aug 27, 2022
2e0fdb7
Fix calculation
15sawyer Aug 27, 2022
2e9ccab
DB method signature change
Rakambda Aug 28, 2022
311ebe6
Change character set for all MariaDB tables.
15sawyer Aug 28, 2022
f708450
Merge branch 'main' of github.com:15sawyer/ChannelPointsMiner
15sawyer Aug 28, 2022
d06c7a1
Send EventUpdatedEvent on unknown events.
15sawyer Aug 29, 2022
2050696
User predictions are now deleted after prediction was resolved.
15sawyer Aug 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
/out
/streamers
/target
bin/

*.iml
/analytics.db
/config.json
/log4j2.xml
26 changes: 23 additions & 3 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Other forms of point, like those provided by bots through IRC (Streamlabs, etc.)
* Follow raids.
* Participate and claim link:https://www.twitch.tv/drops/campaigns[campaigns].
* Join IRC chat.
* Participate in predictions.
* Participate in predictions and record peoples predictions.

Limits:

Expand Down Expand Up @@ -224,9 +224,10 @@ Analytics settings define a way to collect data on your twitch account as time p
This includes:

* Balance evolution
* Predictions made & results
* Your own Predictions made & results
* Predictions from other chat participants and their return-on-investment (only approximate as the bet amount is mostly anonymous).

These are store in an external database which allows any external software to access it and process it they wanted way.
These are stored in an external database which allows any external software to access it and process it the wanted way.

Several database types are supported and listed below.
Each database (logical database for MariaDB, different file for H2/SQLite) will however represent one mined account.
Expand All @@ -243,6 +244,10 @@ You'll therefore have to adjust the settings for each mined account to not point
|database
|<<analytics_database_settings,Analytics database settings>>.
|

|recordChatsPredictions
|If set to true, other peoples predictions will be recorded. This is done from two sources, from the top-predictors list and from chat messages (via their badge). Reading from chat, requires the joinIrc setting to be set to true.
|false
|===

==== Analytics database [[analytics_database_settings]]
Expand Down Expand Up @@ -270,6 +275,12 @@ NOTE: Can be omitted if no account

NOTE: Can be omitted if no password
|

|maxPoolSize
|Maximum number of connections to the database.

NOTE: For SQLite connection, value 1 will be forced.
|10
|===

=== Streamer settings [[streamer_settings]]
Expand Down Expand Up @@ -472,6 +483,15 @@ This is the same as "the outcome with higher odds".
|Outcome with the person that placed the biggest prediction overall.
|

|mostTrusted
|Choose the outcome that's backed by other users with the highest average return-on-investment. *Requires at least some data gathered beforehand via the 'recordChatsPredictions' setting!* E.g. only users with at least 5 made predictions are taken into account by default.
|Take the return-on-investment from people who already placed a prediction, calculate the average per outcome, then pick the highest.
|* minTotalBetsPlacedByUser: only user with at least this number of bets are considered in the calculation. Default is 5.

* minTotalBetsPlacedOnPrediction: if not enough bets were placed, skip this prediction. Default is 10.

* minTotalBetsPlacedOnOutcome: if not enough bets were placed on the chosen outcome, skip this prediction. Default is 5.

|smart
|Choose the outcome with the most users.
However, if the two most picked outcomes have a user count similar, choose the outcome with the least points (higher odds).
Expand Down
Empty file modified gradlew
100644 → 100755
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ public interface ITwitchChatClient extends AutoCloseable{

@Override
void close();

void addChatMessageListener(@NotNull ITwitchChatMessageListener listener);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package fr.raksrinana.channelpointsminer.miner.api.chat;

import org.jetbrains.annotations.NotNull;

public interface ITwitchChatMessageListener{

void onChatMessage(@NotNull String streamer, @NotNull String actor, @NotNull String message);

void onChatMessage(@NotNull String streamer, @NotNull String actor, @NotNull String message, @NotNull String badges);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package fr.raksrinana.channelpointsminer.miner.api.chat;

import fr.raksrinana.channelpointsminer.miner.event.impl.ChatMessageEvent;
import fr.raksrinana.channelpointsminer.miner.factory.TimeFactory;
import fr.raksrinana.channelpointsminer.miner.miner.IMiner;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;

@RequiredArgsConstructor
public class TwitchChatEventProducer implements ITwitchChatMessageListener{
@NotNull
private final IMiner miner;

@Override
public void onChatMessage(@NotNull String streamer, @NotNull String actor, @NotNull String message){
onChatMessage(streamer, actor, message, "");
}

@Override
public void onChatMessage(@NotNull String streamer, @NotNull String actor, @NotNull String message, @NotNull String badges){
var event = new ChatMessageEvent(miner, TimeFactory.now(), streamer, actor, message, badges);
miner.onEvent(event);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package fr.raksrinana.channelpointsminer.miner.api.chat.irc;

import fr.raksrinana.channelpointsminer.miner.api.chat.ITwitchChatClient;
import fr.raksrinana.channelpointsminer.miner.api.chat.ITwitchChatMessageListener;
import fr.raksrinana.channelpointsminer.miner.api.passport.TwitchLogin;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.kitteh.irc.client.library.Client;
import org.kitteh.irc.client.library.defaults.element.messagetag.DefaultMessageTagLabel;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
Expand All @@ -15,11 +19,18 @@
@Log4j2
public class TwitchIrcChatClient implements ITwitchChatClient{

private static final String TAGS_CAPABILITY = "twitch.tv/tags";
private static final String EMOTE_SETS_TAG_NAME = "emote-sets";

@NotNull
private final TwitchLogin twitchLogin;
private final boolean listenMessages;
private final Collection<ITwitchChatMessageListener> chatMessageListeners = new LinkedList<>();

@Nullable
private Client ircClient;
@Nullable
private TwitchIrcMessageHandler ircMessageHandler;

@Override
public void join(@NotNull String channel){
Expand All @@ -38,20 +49,10 @@ public void join(@NotNull String channel){
public void joinPending(){
}

private synchronized Client getIrcClient(){
if(Objects.isNull(ircClient)){
log.info("Creating new Twitch IRC client");

ircClient = TwitchIrcFactory.createIrcClient(twitchLogin);
ircClient.connect();
ircClient.setExceptionListener(e -> log.error("Error from irc", e));

ircClient.getEventManager().registerEventListener(TwitchIrcFactory.createIrcListener(twitchLogin.getUsername()));

log.info("IRC Client created");
}

return ircClient;
@Override
public void addChatMessageListener(@NotNull ITwitchChatMessageListener listener){
chatMessageListeners.add(listener);
Optional.ofNullable(ircMessageHandler).ifPresent(i -> i.addListener(listener));
}

@Override
Expand Down Expand Up @@ -79,4 +80,35 @@ public void ping(){
public void close(){
Optional.ofNullable(ircClient).ifPresent(Client::shutdown);
}

@NotNull
private synchronized Client getIrcClient(){
if(Objects.isNull(ircClient)){
log.info("Creating new Twitch IRC client");

ircClient = TwitchIrcFactory.createIrcClient(twitchLogin);
ircClient.connect();
ircClient.setExceptionListener(e -> log.error("Error from irc", e));

var eventManager = ircClient.getEventManager();
eventManager.registerEventListener(TwitchIrcFactory.createIrcConnectionHandler(twitchLogin.getUsername()));

if(listenMessages){
ircMessageHandler = TwitchIrcFactory.createIrcMessageHandler(twitchLogin.getUsername());
chatMessageListeners.forEach(ircMessageHandler::addListener);
eventManager.registerEventListener(ircMessageHandler);

var capabilityRequest = ircClient.commands().capabilityRequest();
capabilityRequest.enable(TAGS_CAPABILITY);
capabilityRequest.execute();

var tagManager = ircClient.getMessageTagManager();
tagManager.registerTagCreator(TAGS_CAPABILITY, EMOTE_SETS_TAG_NAME, DefaultMessageTagLabel.FUNCTION);
}

log.info("IRC Client created");
}

return ircClient;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@

@RequiredArgsConstructor
@Log4j2
public class TwitchIrcEventListener{
public class TwitchIrcConnectionHandler{

@NotNull
private final String accountName;

@Handler
Expand All @@ -20,7 +22,7 @@ private void onClientConnectionEstablishedEvent(ClientNegotiationCompleteEvent e
log.info("IRC client connected");
}
}

@Handler
public void onClientConnectionCLoseEvent(ClientConnectionClosedEvent event){
try(var ignored = LogContext.with(accountName)){
Expand All @@ -33,7 +35,7 @@ public void onClientConnectionCLoseEvent(ClientConnectionClosedEvent event){
}
}
}

@Handler
public void onChannelJoinEvent(@NotNull RequestedChannelJoinCompleteEvent event){
try(var ignored = LogContext.with(accountName)){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class TwitchIrcFactory{
private static final String TWITCH_IRC_HOST = "irc.chat.twitch.tv";
@NotNull
public static Client createIrcClient(@NotNull TwitchLogin twitchLogin){
return createIrcClient(twitchLogin.getUsername(), "oauth:%s".formatted(twitchLogin.getAccessToken()));
}
@NotNull
public static Client createIrcClient(@NotNull TwitchLogin twitchLogin){
return createIrcClient(twitchLogin.getUsername(), "oauth:%s".formatted(twitchLogin.getAccessToken()));
}
@NotNull
private static Client createIrcClient(@NotNull String username, @Nullable String password){
var client = Client.builder()
Expand All @@ -31,7 +31,12 @@ private static Client createIrcClient(@NotNull String username, @Nullable String
}

@NotNull
public static TwitchIrcEventListener createIrcListener(@NotNull String accountName){
return new TwitchIrcEventListener(accountName);
public static TwitchIrcConnectionHandler createIrcConnectionHandler(@NotNull String accountName){
return new TwitchIrcConnectionHandler(accountName);
}

@NotNull
public static TwitchIrcMessageHandler createIrcMessageHandler(@NotNull String accountName){
return new TwitchIrcMessageHandler(accountName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package fr.raksrinana.channelpointsminer.miner.api.chat.irc;

import fr.raksrinana.channelpointsminer.miner.api.chat.ITwitchChatMessageListener;
import fr.raksrinana.channelpointsminer.miner.log.LogContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import net.engio.mbassy.listener.Handler;
import org.jetbrains.annotations.NotNull;
import org.kitteh.irc.client.library.event.channel.ChannelMessageEvent;
import java.util.Collection;
import java.util.LinkedList;

@RequiredArgsConstructor
@Log4j2
public class TwitchIrcMessageHandler{

@NotNull
private final String accountName;

@NotNull
private final Collection<ITwitchChatMessageListener> listeners = new LinkedList<>();

@Handler
public void onMessageEvent(@NotNull ChannelMessageEvent event){
try(var ignored = LogContext.with(accountName)){
log.trace("Received Irc Chat Message");
var badges = event.getTag("badges");
if(badges.isPresent()){
listeners.forEach(l -> l.onChatMessage(
event.getChannel().getName().substring(1),
event.getActor().getMessagingName(),
event.getMessage(),
badges.get().getAsString()));
}
else{
listeners.forEach(l -> l.onChatMessage(
event.getChannel().getName().substring(1),
event.getActor().getMessagingName(),
event.getMessage()));
}
}
}

public void addListener(ITwitchChatMessageListener listener){
listeners.add(listener);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public interface ITwitchChatWebSocketListener{
public interface ITwitchChatWebSocketClosedListener{
void onWebSocketClosed(@NotNull TwitchChatWebSocketClient client, int code, @Nullable String reason, boolean remote);
}
Loading