From 89f81d7b89666dff094d72abb4a1be75e541bd1e Mon Sep 17 00:00:00 2001 From: Steve Ayerhart Date: Sun, 4 Apr 2021 11:10:32 -0400 Subject: [PATCH] add bbb and nhl --- README.txt | 1 + bonerbonerboner/.gitignore | 1 + bonerbonerboner/bonerbonerboner.factor | 29 ++ .../services/link-logger/link-logger.factor | 53 ++++ .../services/mississippis/mississippis.factor | 83 ++++++ .../services/platzisms/platzisms.factor | 61 +++++ bonerbonerboner/services/services.factor | 14 + bonerbonerboner/services/slack/slack.factor | 64 +++++ bonerbonerboner/templates/themes.xml | 9 + nhl/nhl.factor | 256 ++++++++++++++++++ 10 files changed, 571 insertions(+) create mode 100644 README.txt create mode 100644 bonerbonerboner/.gitignore create mode 100644 bonerbonerboner/bonerbonerboner.factor create mode 100644 bonerbonerboner/services/link-logger/link-logger.factor create mode 100644 bonerbonerboner/services/mississippis/mississippis.factor create mode 100644 bonerbonerboner/services/platzisms/platzisms.factor create mode 100644 bonerbonerboner/services/services.factor create mode 100644 bonerbonerboner/services/slack/slack.factor create mode 100644 bonerbonerboner/templates/themes.xml create mode 100644 nhl/nhl.factor diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..fd1af07 --- /dev/null +++ b/README.txt @@ -0,0 +1 @@ +The 'work' directory is for your own personal vocabularies. diff --git a/bonerbonerboner/.gitignore b/bonerbonerboner/.gitignore new file mode 100644 index 0000000..e6ba4bb --- /dev/null +++ b/bonerbonerboner/.gitignore @@ -0,0 +1 @@ +/bbb.db diff --git a/bonerbonerboner/bonerbonerboner.factor b/bonerbonerboner/bonerbonerboner.factor new file mode 100644 index 0000000..0ca363d --- /dev/null +++ b/bonerbonerboner/bonerbonerboner.factor @@ -0,0 +1,29 @@ +! Copyright (C) 2020 . +! See http://factorcode.org/license.txt for BSD license. + +USING: kernel http.server http.server.responses http.server.dispatchers furnace.actions html.forms accessors namespaces io.servers io.sockets.secure.debug ; +USING: bonerbonerboner.services ; + +IN: bonerbonerboner + +TUPLE: bbb < dispatcher ; + +: ( -- action ) + + [ "bbb" "theme" set-value ] >>init + + { bbb "templates/themes" } >>template ; + + +: ( -- response ) + "bonerbonerboner" ; + +: ( -- responder ) + bbb new-dispatcher + "heartbeat" add-responder ; +! "theme" add-responder ; + +SYMBOL: current-test-server +: run-test-bbb ( -- ) + main-responder set-global + 8080 httpd current-test-server set ; diff --git a/bonerbonerboner/services/link-logger/link-logger.factor b/bonerbonerboner/services/link-logger/link-logger.factor new file mode 100644 index 0000000..ba424d6 --- /dev/null +++ b/bonerbonerboner/services/link-logger/link-logger.factor @@ -0,0 +1,53 @@ +! Copyright (C) 2020 . +! See http://factorcode.org/license.txt for BSD license. + +USING: kernel sequences http http.server.dispatchers accessors vocabs.parser furnace.actions regexp db.types db.tuples math urls strings calendar ; +USING: bonerbonerboner ; + +IN: bonerbonerboner.services.link-logger + +! TODO: index url and updated date +! TODO: add slack integration + +TUPLE: link id url created-by updated-by repost-count date-created date-updated ; + +link "links" +{ + { "id" "id" +db-assigned-id+ } + { "url" "url" URL } + { "repost-count" "repost_count" INTEGER } + { "created-by" "created_by" TEXT } + { "updated-by" "updated_by" TEXT } + { "date-created" "date_created" TIMESTAMP } + { "date-updated" "date_updated" TIMESTAMP } +} define-persistent + +: ensure-link-logger ( -- ) + [ link ensure-table ] with-bbb-db ; + +: ( url who -- link ) + [ f ] 2dip dup 0 now dup link boa ; + +: ( link who -- link ) + [ dup repost-count>> 1 + >>repost-count ] dip >>updated-by now >>date-updated ; + +: add-link ( url who -- ) + [ insert-tuple ] with-bbb-db ; + +: update-repost ( link who -- ) + [ update-tuple ] with-bbb-db ; + +: repost? ( url -- link/f ) + link new swap >>url [ select-tuple ] with-bbb-db ; + +: url-regex ( -- regexp ) + R/ http[s]?:\/\/(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+/i ; + +: strip-urls ( str -- seq ) + url-regex all-matching-subseqs ; + +: check-repost ( url who -- ) + [ dup repost? ] dip swap [ update-repost ] [ add-link ] if ; + +! : check-links ( str -- ) + ! strip-urls [ check-repost ] each ; diff --git a/bonerbonerboner/services/mississippis/mississippis.factor b/bonerbonerboner/services/mississippis/mississippis.factor new file mode 100644 index 0000000..455dcde --- /dev/null +++ b/bonerbonerboner/services/mississippis/mississippis.factor @@ -0,0 +1,83 @@ +! Copyright (C) 2020 . +! See http://factorcode.org/license.txt for BSD license. +USING: kernel math math.ranges math.order math.parser math.functions arrays sequences sequences.extras threads calendar io timers accessors random formatting combinators combinators.random http.server.responses ; +USING: prettyprint http furnace.actions http.server.dispatchers ; +USING: bonerbonerboner.services.slack ; + +IN: bonerbonerboner.services.mississippis + +CONSTANT: max-mississippi-count 30 +CONSTANT: min-mississippi-count 1 + +: random-stupid-word ( -- something-stupid ) + { "balls" "fart" "SIEEEEEEEEEEGE" "hey" "HONESTLY" "fuckface" "it's too late-TOOOOO LATE" } + random ; + +: random-m-word ( -- m-word ) + { "Minnesota" "Montana" "Marsupial" "Minneapolis" } + random ; + +: random-no-word ( -- nope ) + { "No can doobie" "Can't do that" "I don't think so, kev" "nope" "Can't, learn the rules" } + random ; + +: nonstandard-mississippi-duration ( -- duration ) + .25 5 uniform-random-float seconds ; + +: standard-mississippi-duration ( -- duration ) + 1 seconds ; + +: nonstandard-mississippi-wait ( -- ) + nonstandard-mississippi-duration sleep ; + +: valid-mississippi-count? ( n -- ? ) + min-mississippi-count max-mississippi-count between? ; + +: announce ( str -- ) + . flush ; +! say-slack ; + +: announce-sip ( sip -- ) + "%d Mississippi" sprintf announce ; + +: announce-fake-terminal ( terminal -- ) + number>string random-m-word 2array " " join announce ; + +: announce-random-stupid-word ( -- ) + random-stupid-word announce ; + +: announce-random-sip ( -- ) + 1000 random announce-sip ; + +: announce-go! ( -- ) + "GO!" announce ; + +: announce-standard-mississippis ( terminal -- ) + [1,b] [ standard-mississippi-duration sleep announce-sip ] each ; + +:: announce-nonstandard-mississippi ( terminal sip -- ) + { + { .3 [ ] } + { .1 [ announce-random-sip ] } + { .075 [ announce-random-stupid-word ] } + { .025 [ terminal announce-fake-terminal ] } + { .5 [ sip announce-sip ] } + } casep ; + +: announce-nonstandard-mississippis ( terminal -- ) + [ + 1 - [ dup ] [ [1,b] ] bi + [ nonstandard-mississippi-wait announce-nonstandard-mississippi ] 2each + ] + [ announce-sip ] bi ; + +: announce-mississippis ( terminal standard? -- ) + [ announce-standard-mississippis ] [ announce-nonstandard-mississippis ] if ; + +: mississippi-go! ( standard? terminal -- ) + dup valid-mississippi-count? + [ announce-go! swap announce-mississippis ] + [ 2drop random-no-word announce ] if ; + +: ( -- response ) + "mississippis" ; diff --git a/bonerbonerboner/services/platzisms/platzisms.factor b/bonerbonerboner/services/platzisms/platzisms.factor new file mode 100644 index 0000000..0f549f2 --- /dev/null +++ b/bonerbonerboner/services/platzisms/platzisms.factor @@ -0,0 +1,61 @@ +! Copyright (C) 2020 . +! See http://factorcode.org/license.txt for BSD license. +USING: kernel regexp sequences http.server http.server.responses db db.types db.tuples unicode formatting assocs ; +USING: bonerbonerboner bonerbonerboner.services.slack ; + +IN: bonerbonerboner.services.platzisms + +TUPLE: platzism id quote ; + +platzism "platzisms" +{ + { "id" "id" +db-assigned-id+ } + { "quote" "quote" TEXT } +} define-persistent + +: ensure-platzisms ( -- ) + [ platzism ensure-table ] with-bbb-db ; + +: ( str -- platzism ) + [ f ] dip platzism boa ; + +: add-platzism ( str -- ) + [ insert-tuple ] with-bbb-db ; + +: platzism-exists? ( str -- ? ) + [ >lower "SELECT id FROM platzisms WHERE lower(quote) = '%s'" sprintf sql-query ] with-bbb-db empty? not ; + +: is-platz? ( str -- ? ) + R/ ^platz\?$/i matches? ; + +: is-platzism? ( str -- ? ) + R/ ^steve platz is currently .+/i matches? ; + +: random-platzism ( -- platzism ) + "SELECT quote from platzisms ORDER BY RANDOM() LIMIT 1" [ sql-query ] with-bbb-db + dup empty? [ drop "I'm not sure what Platz is up to." ] [ first first ] if ; + +: repost-callout ( -- ) + "We already know Platz is doing that" say-slack ; + +: confirm-platzism ( -- ) + "noted" say-slack ; + +: share-platzism ( -- ) + random-platzism say-slack ; + +: log/confirm-platzism ( str -- ) + dup platzism-exists? + [ drop repost-callout ] + [ + add-platzism + confirm-platzism + ] if ; + +: check-platz ( event -- ) + "text" of + [ is-platz? [ share-platzism ] when ] + [ is-platzism? [ log/confirm-platzism ] when* ] bi ; + +[ check-platz ] add-slack-handler + diff --git a/bonerbonerboner/services/services.factor b/bonerbonerboner/services/services.factor new file mode 100644 index 0000000..9210c33 --- /dev/null +++ b/bonerbonerboner/services/services.factor @@ -0,0 +1,14 @@ +! Copyright (C) 2020 . +! See http://factorcode.org/license.txt for BSD license. + +USING: kernel fry db db.sqlite environment math.parser ; + +IN: bonerbonerboner.services + +CONSTANT: bbb-default-port 8069 + +: with-bbb-db ( quot -- ) + '[ "bbb.db" _ with-db ] call ; inline + +: bbb-api-port ( -- port ) + "BBB_API_PORT" os-env [ string>number ] [ bbb-default-port ] if* ; diff --git a/bonerbonerboner/services/slack/slack.factor b/bonerbonerboner/services/slack/slack.factor new file mode 100644 index 0000000..742749d --- /dev/null +++ b/bonerbonerboner/services/slack/slack.factor @@ -0,0 +1,64 @@ +! Copyright (C) 2020 . +! See http://factorcode.org/license.txt for BSD license. + +USING: io.sockets.secure kernel http http.server http.server.dispatchers accessors furnace.actions namespaces http.server.responses io.servers io.sockets.secure.debug json.reader assocs combinators sequences arrays threads locals formatting json.writer urls http.client hashtables ; +USING: prettyprint ; + +IN: bonerbonerboner.services.slack + +SYMBOLS: slack-callbacks current-slack-webhook-url current-slack-authorization-token ; + +TUPLE: slack < dispatcher ; + +: add-slack-handler ( quot: ( event -- ) -- ) + slack-callbacks get append slack-callbacks set ; + +: ( -- response ) + "400" "unrecognized event type" ; + +: ( -- response ) + "slack" ; + +: ( -- response ) + "200" "OK" ; + +: handle-challenge ( json -- response ) + "challenge" of ; + +:: handle-slack-event ( slack-event -- response ) + "bot_id" slack-event key? "subtype" slack-event key? or + [ + slack-callbacks get length slack-event + slack-callbacks get [ curry "Slack Callback" spawn drop ] 2each + ] unless + ; + +: ( -- action ) + + [ + request get post-data>> data>> json> dup + "type" of + { + { "url_verification" [ handle-challenge ] } + { "event_callback" [ handle-slack-event ] } + [ drop ] + } case + ] >>submit ; + +: ( -- action ) + [ ] >>display ; + +: slack-post-message ( payload -- ) + >json + current-slack-webhook-url get >url + + http-request 2drop ; + +: say-slack ( str -- ) + "text" associate + slack-post-message ; + +: ( -- responder ) + slack new-dispatcher + "slack-events" add-responder + "heartbeat" add-responder ; diff --git a/bonerbonerboner/templates/themes.xml b/bonerbonerboner/templates/themes.xml new file mode 100644 index 0000000..a64f114 --- /dev/null +++ b/bonerbonerboner/templates/themes.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/nhl/nhl.factor b/nhl/nhl.factor new file mode 100644 index 0000000..8103162 --- /dev/null +++ b/nhl/nhl.factor @@ -0,0 +1,256 @@ +! Copyright (C) 2020 . +! See http://factorcode.org/license.txt for BSD license. +USING: kernel math math.parser strings calendar alien.syntax sequences urls http.client formatting json.reader assocs combinators splitting classes.maybe ; +USING: prettyprint ; + +IN: nhl + +TUPLE: conference + { id integer } + { name string } + { short-name string } + { abbreviation string } ; + +TUPLE: timezone + { id string } + { offset integer } + { tz string } ; + +TUPLE: venue + { name: string } + { city: string } + { timezone timezone } ; + +TUPLE: division + { id integer } + { name string } +! { name-short string } + { abbreviation string } + { conference maybe{ conference } } ; + +TUPLE: franchise + { id integer } + { team-name string } + { first-season-id integer } + { location-name string } + { most-recent-team-id integer } ; + +TUPLE: team + { id integer } + { name string } + { team-name string } + { location-name string } + { short-name string } + { official-site-url url } + { abbreviation string } + ! { first-year timestamp } + { first-year integer } + { division division } + { franchise franchise } + { venue venue } ; + +TUPLE: game-time + { period integer } + { period-sec integer } ; +! { period-sec timestamp } ; + +TUPLE: location + { x integer } + { y integer } ; + +TUPLE: player-position + { code string } + { name string } + { type string } + { abbreviation string } ; + +TUPLE: player + { id integer } + { full-name string } + { first-name string } + { last-name string } + { primary-number string } + { current-team string } + { position player-position } + { height string } + { weight integer } + { shoots/catches string } + { alternate-captain? boolean } + { captain? boolean } + { rookie? boolean } + { nationality string } + { birth-date timestamp } + { birth-city string } + { birth-state/prvince maybe{ string } } + { birth-country string } ; + +TUPLE: status + { abstract-state string } + { coded-state string } + { detailed-state string } + { code string } + { start-time-tbd boolean } ; + +TUPLE: league-record + { wins integer } + { losses integer } + { ot integer } + { type string } ; + +TUPLE: team-info + { scores integer } + { attempts integer } + { goals integer } + { shots-on-goal integer } + { rink-side string } + { goalie-pulled? boolean } + { num-skaters integer } + { power-play? boolean } + { league-record league-record } + { score integer } + { team team } ; + +TUPLE: teams + { } + +TUPLE: game + { id integer } + { link team } + { type team } + { season string } + { date timestamp } + { status status } + { teams teams } + { linescore linescore } + { venue venue } + { content content } + { series-summary series-summary } ; + +TUPLE: event + { game-id integer } + { id integer } + { type string } + { sub-type string } + { time game-time } + { location location } + { team team } + { by sequence } + { on player } ; + +TUPLE: date + { date string } + { total-items integer } + { total-events integer } + { total-games integer } + { total-matches integer } + { games sequence } + { events sequence } + { matches sequence } ; + +TUPLE: schedule + { copyright string } + { total-items integer } + { total-events integer } + { total-games integer } + { total-matches integer } + { wait integer } + { dates sequence } ; + +CONSTANT: nhl-base-url "https://statsapi.web.nhl.com/api/v1/" +! TODO: CACHING + +: >entity-url ( entity id -- url ) + dup string? [ string>number ] when + "%s/%d" sprintf nhl-base-url prepend >url ; + +: entity-get ( id entity -- json ) + swap over [ + >entity-url http-get nip json> + ] dip of first ; + +: entities-get ( entity -- seq ) + dup [ + nhl-base-url prepend http-get nip json> + ] dip of ; + +: parse-conference ( json -- conference ) + "CONF" . dup . + { + [ "id" of ] [ "name" of ] [ "shortName" of ] [ "abbreviation" of ] + } cleave + conference boa ; + +: conference-get ( id -- conference ) "conferences" entity-get parse-conference ; +: conferences-get ( -- conferences ) "conferences" entities-get [ parse-conference ] map ; + +: parse-division ( json -- division ) + { + [ "id" of ] [ "name" of ] [ "abbreviation" of ] +! [ "conference" of ] ! "id" of conference-get ] + } cleave + f + division boa ; + +: parse-franchise ( json -- franchise ) + { + [ "franchiseId" of ] [ "teamName" of ] [ "firstSeasonId" of ] + [ "locationName" of ] [ "mostRecentTeamId" of ] + } cleave + franchise boa ; + +: parse-position ( json -- player-position ) + { + [ "code" of ] [ "name" of ] [ "type" of ] [ "abbreviation" of ] + } cleave player-position boa ; + +: parse-player ( json -- player ) + { + [ "id" of ] + [ "fullName" of ] + [ "firstName" of ] + [ "lastName" of ] + [ "primaryNumber" of ] + [ "currentTeam" of "name" of ] + [ "primaryPosition" of parse-position ] + [ "height" of ] + [ "weight" of ] + [ "shootsCatches" of ] + [ "alternateCaptain" of ] + [ "captain" of ] + [ "rookie" of ] + [ "nationality" of ] + [ "birthDate" of "-" split [ string>number ] map [ first ] [ second ] [ third ] tri ] + [ "birthCity" of ] + [ "birthStateProvince" of ] + [ "birthCountry" of ] + } cleave player boa ; + +: division-get ( id -- division ) + "divisions" entity-get parse-division ; +: divisions-get ( -- divisions ) + "divisions" entities-get [ parse-division ] map ; + +: franchise-get ( id -- division ) + "franchises" entity-get parse-franchise ; +: franchises-get ( -- divisions ) + "franchises" entities-get [ parse-franchise ] map ; + +: player-get ( id -- player ) "people" entity-get parse-player ; +: players-get ( ids -- players ) [ player-get ] map ; + +: parse-timezone ( json -- timezone ) + [ "id" of ] [ "offset" of ] [ "tz" of ] tri timezone boa ; +: parse-venue ( json -- venue ) + [ "name" of ] [ "city" of ] [ "timeZone" of parse-timezone ] tri venue boa ; + +: parse-team ( json -- team ) + { + [ "id" of ] [ "name" of ] [ "teamName" of ] [ "locationName" of ] + [ "shortName" of ] [ "officialSiteUrl" of >url ] [ "abbreviation" of ] + [ "firstYearOfPlay" of string>number ] [ "division" of "id" of division-get ] + [ "franchiseId" of franchise-get ] [ "venue" of parse-venue ] + } cleave + team boa ; + +: team-get ( id -- team ) "teams" entity-get parse-team ; +: teams-get ( -- teams ) "teams" entities-get [ parse-team ] map ;