Skip to content

Commit

Permalink
HQ Words support
Browse files Browse the repository at this point in the history
  • Loading branch information
taylorfinnell committed Dec 2, 2018
1 parent 6fc7e73 commit a15381f
Show file tree
Hide file tree
Showing 33 changed files with 1,128 additions and 16 deletions.
23 changes: 17 additions & 6 deletions README.md
@@ -1,6 +1,6 @@
# hqtrivia

A small framework for building HQ Trivia bots.
A small framework for building HQ Trivia and HQ Words bots.

## Installation

Expand Down Expand Up @@ -45,19 +45,30 @@ HqTrivia.on_show(coordinator) do |show|
end
```

Other messages that can be handled include
**HQ Trivia Messagges**

- `HqTriva::Model::Question`
- `HqTriva::Model::QuestionSummary`
- `HqTriva::Model::QuestionClosed`
- `HqTriva::Model::QuestionFinished`
- `HqTriva::Model::GameSummary`

**HQ Words Messaegs**

- `HqTriva::Model::ShowWheel`
- `HqTriva::Model::HideWheel`
- `HqTriva::Model::StartRound`
- `HqTriva::Model::EndRound`
- `HqTriva::Model::WordsGameResult`

**Shared Messages**

- `HqTriva::Model::QuestionClosed`
- `HqTriva::Model::PostGame`
- `HqTriva::Model::Interaction`
- `HqTriva::Model::GameSummary`
- `HqTriva::Model::UnknownMessage`
- `HqTriva::Model::BroadcastStats`
- `HqTriva::Model::BroadcastEnded`
- `HqTriva::Model::UnknownMessage`

- `HqTriva::Model::Kicked`

## Contributors

Expand Down
2 changes: 1 addition & 1 deletion shard.yml
@@ -1,5 +1,5 @@
name: hqtrivia
version: 0.2.6
version: 0.2.7

authors:
- Taylor Finnell <tmfinnell@gmail.com>
Expand Down
838 changes: 838 additions & 0 deletions spec/data/words

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions spec/hqtrivia/bot_spec.cr
Expand Up @@ -18,6 +18,33 @@ class MyBot
end
end

class WordsBot
include HqTrivia::Bot

getter round_starts
getter round_ends
getter reveals

def initialize(@show : HqTrivia::Model::Show, @coordinator : HqTrivia::Coordinator)
super
@round_starts = 0
@round_ends = 0
@reveals = 0
end

def handle_message(message : HqTrivia::Model::StartRound)
@round_starts += 1
end

def handle_message(message : HqTrivia::Model::EndRound)
@round_ends += 1
end

def handle_message(message : HqTrivia::Model::LetterReveal)
@reveals += 1
end
end

module HqTrivia
describe Bot do
it "works" do
Expand All @@ -34,5 +61,18 @@ module HqTrivia
bot.prize.should eq(show.prize)
bot.country.should eq("us")
end

it "works with words" do
messages = File.read("./spec/data/words").each_line.to_a
show = Model::Show.new(active: true, show_type: "hq-us", game_type: "words", prize: 100, show_id: 666, start_time: Time.now)
connection = Connection::Local.new(messages)

bot = WordsBot.new(show, LocalCoordinator.new("us"))
bot.play(connection)

bot.reveals.should eq(16)
bot.round_starts.should eq(8)
bot.round_ends.should eq(8)
end
end
end
6 changes: 6 additions & 0 deletions src/hqtrivia.cr
@@ -1,23 +1,29 @@
require "./hqtrivia/*"

# Framework for creating HQ Trivia and HQ Words bots
module HqTrivia
# :nodoc:
@@LOGGER = Logger.new

# :nodoc:
@@AUTH = Auth.new

# HqTrivia logging instance
def self.logger
@@LOGGER
end

# Set the logger to an IO
def self.logger=(logger)
@@LOGGER = logger
end

# Auth singleton
def self.auth
@@AUTH
end

# When an active `Show` is seen, it's yielded to the block
def self.on_show(coordinator : Coordinator, blocking = true, &block : Model::Show ->)
show_coordinator = ShowCoordinator.new(coordinator)
show_coordinator.on_show(blocking, &block)
Expand Down
16 changes: 15 additions & 1 deletion src/hqtrivia/auth.cr
@@ -1,9 +1,23 @@
module HqTrivia
# Manages bearer tokens for various countries
class Auth
# Given a *country* it returns the bearer token from the ENV
def header(country : String)
HTTP::Headers{"Authorization" => "Bearer #{token(country)}"}
HTTP::Headers{
"Authorization" => "Bearer #{token(country)}",
"x-hq-device" => "iPhone10,4",
"x-hq-client" => "iOS/1.3.27 b121",
"accept-language" => "en-us",
"x-hq-stk" => "MQ==",
"x-hq-deviceclass" => "phone",
"x-hq-timezone" => "America/Chicago",
"user-agent" => "HQ-iOS/121 CFNetwork/975.0.3 Darwin/18.2.0",
"x-hq-country" => "us",
"x-hq-lang" => "en",
}
end

# :nodoc:
private def token(country : String)
ENV["#{country.upcase}_AUTHORIZATION_TOKEN"]
end
Expand Down
3 changes: 3 additions & 0 deletions src/hqtrivia/bot.cr
Expand Up @@ -6,6 +6,7 @@ module HqTrivia
def initialize(@show : HqTrivia::Model::Show, @coordinator : Coordinator)
end

# Play the game on the given *connection*
def play(connection = HqTrivia::Connection::Hq.new)
HqTrivia.logger.debug("Bot playing #{@coordinator.country} show #{@show.to_json}")

Expand Down Expand Up @@ -50,9 +51,11 @@ module HqTrivia
end
{% end %}

# Called when an unknown message is seen
protected def handle_message(message : Model::UnknownMessage)
end

# Called with raw JSON
protected def handle_message(message : String)
end
end
Expand Down
6 changes: 5 additions & 1 deletion src/hqtrivia/connection/hq.cr
Expand Up @@ -17,19 +17,22 @@ module HqTrivia
@on_message_callback = block
end

# Yields raw JSON to the block
def on_raw_message(&block : String ->)
@on_raw_message_callback = block
end

# Connects to the HQ websocket
def connect(show : Model::Show, coordinator : Coordinator)
if coordinator.current_show.active
show = coordinator.current_show
if show && show.active
open_socket(show, coordinator)
else
HqTrivia.logger.info "Not connecting show (#{coordinator.country}) is no longer active"
end
end

# :nodoc:
private def open_socket(show, coordinator)
HqTrivia.logger.debug("Connecting: #{coordinator.country}")

Expand All @@ -52,6 +55,7 @@ module HqTrivia
socket.run
end

# :nodoc:
private def websocket_headers(coordinator)
HTTP::Headers{
"x-hq-client" => "iOS/1.2.17",
Expand Down
5 changes: 5 additions & 0 deletions src/hqtrivia/connection/interface.cr
@@ -1,8 +1,13 @@
module HqTrivia
module Connection
module Interface
# Called anytime a valid websocket message is sent
abstract def on_message(&block : HqTrivia::Model::WebSocketMessage ->)

# Called on any websocket message, sending the raw json to the block
abstract def on_raw_message(&block : String ->)

# Connect to a *show* using the given *coordinator*
abstract def connect(show : Model::Show, coordinator : Coordinator)
end
end
Expand Down
5 changes: 5 additions & 0 deletions src/hqtrivia/connection/local.cr
@@ -1,19 +1,24 @@
module HqTrivia
module Connection
# Local connection, given an array of messaegs, it parses them and yields
# them to the block
class Local
include Interface

def initialize(@raw_messages : Array(String))
end

# Called anytime a valid websocket message is sent
def on_message(&block : HqTrivia::Model::WebSocketMessage ->)
@on_message_callback = block
end

# Called on any websocket message, sending the raw json to the block
def on_raw_message(&block : String ->)
@on_raw_message_callback = block
end

# Connect to a *show* using the given *coordinator*
def connect(show : Model::Show, coordinator : Coordinator)
@raw_messages.each do |msg|
@on_raw_message_callback.try &.call msg
Expand Down
3 changes: 2 additions & 1 deletion src/hqtrivia/model/broadcast_ended.cr
@@ -1,7 +1,8 @@
module HqTrivia
module Model
# Sent from the server when the broadcast has ended, this can either be
# because the show has actually ended, or your client lost connection.
# because the show has actually ended, or your client lost connection. Used
# in both Trivia and Words
class BroadcastEnded
include WebSocketMessage

Expand Down
3 changes: 2 additions & 1 deletion src/hqtrivia/model/broadcast_stats.cr
@@ -1,7 +1,7 @@
module HqTrivia
module Model
# Sent from the server containing various statistics about the broadcast at
# that point in time.
# that point in time. Used in both Trivia and Words
class BroadcastStats
include WebSocketMessage

Expand All @@ -14,6 +14,7 @@ module HqTrivia
sent: Time,
})

# Information about player counts, etc
class ViewerCounts
JSON.mapping({
connected: Int32,
Expand Down
23 changes: 23 additions & 0 deletions src/hqtrivia/model/end_round.cr
@@ -0,0 +1,23 @@
module HqTrivia
module Model
# Represents a round end in HQ Words
class EndRound
include WebSocketMessage

JSON.mapping({
type: String,
ts: Time,
answer: Array(String),
hint: String,
show_id: {key: "showId", type: Int32},
round_id: {key: "roundId", type: Int32},
round_number: {key: "roundNumber", type: Int32},
round_duration_ms: {key: "roundDurationMs", type: Int32},
correct_answers: {key: "correctAnswers", type: Int32},
incorrect_answers: {key: "incorrectAnswers", type: Int32},
c: Int32,
sent: Time,
})
end
end
end
1 change: 1 addition & 0 deletions src/hqtrivia/model/game_summary.cr
Expand Up @@ -16,6 +16,7 @@ module HqTrivia
sent: Time,
})

# Winner information
class Winner
JSON.mapping({
name: String,
Expand Down
17 changes: 17 additions & 0 deletions src/hqtrivia/model/hide_wheel.cr
@@ -0,0 +1,17 @@
module HqTrivia
module Model
# Not sure, but used in HQ Words
class HideWheel
include WebSocketMessage

JSON.mapping({
type: String,
ts: Time,
show_id: {key: "showId", type: Int32},
round_id: {key: "roundId", type: Int32},
c: Int32,
sent: Time,
})
end
end
end
4 changes: 4 additions & 0 deletions src/hqtrivia/model/int_coerce.cr
@@ -1,6 +1,9 @@
module HqTrivia
module Model
# For coercing string ints to actual ints
class IntCoerce
# Attempts to coerce a string int, ie "123" into an actual int. Works
# even if it already is an Int32
def self.from_json(json : JSON::PullParser)
if val = json.read?(Int32)
val
Expand All @@ -9,6 +12,7 @@ module HqTrivia
end
end

# Converts an Int32 back to a string
def self.to_json(value, builder)
builder.string value.to_s
end
Expand Down
3 changes: 2 additions & 1 deletion src/hqtrivia/model/interaction.cr
@@ -1,7 +1,7 @@
module HqTrivia
module Model
# Sent from the server when a user intercts with the game, for instance a
# chat message.
# chat message. Used in both Trivia and Words
class Interaction
include WebSocketMessage

Expand All @@ -14,6 +14,7 @@ module HqTrivia
sent: Time,
})

# User metadata
class Metadata
JSON.mapping({
user_id: {key: "userId", type: Int32},
Expand Down
19 changes: 19 additions & 0 deletions src/hqtrivia/model/letter_reveal.cr
@@ -0,0 +1,19 @@
module HqTrivia
module Model
# When a letter is shown in HQ Words
class LetterReveal
include WebSocketMessage

JSON.mapping({
type: String,
ts: Time,
show_id: {key: "showId", type: Int32},
round_id: {key: "roundId", type: Int32},
puzzle_state: {key: "puzzleState", type: Array(String)},
reveal: String,
c: Int32,
sent: Time,
})
end
end
end
4 changes: 3 additions & 1 deletion src/hqtrivia/model/message_types.cr
Expand Up @@ -2,9 +2,11 @@ require "json"

module HqTrivia
module Model
# :nodoc:
class MessageTypes
# :nodoc:
MESSAGE_LIST = %w(broadcastEnded broadcastStats gameSummary interaction postGame question
questionClosed questionFinished questionSummary kicked)
questionClosed questionFinished questionSummary kicked endRound hideWheel letterReveal showWheel startRound wordsGameResult)
end
end
end

0 comments on commit a15381f

Please sign in to comment.