Compare commits
	
		
			No commits in common. "main" and "onedb" have entirely different histories.
		
	
	
		
							
								
								
									
										27
									
								
								README.md
								
								
								
								
							
							
						
						
									
										27
									
								
								README.md
								
								
								
								
							|  | @ -1,29 +1,24 @@ | ||||||
| # mlbstats-mastodon-scripts | # mlblive-mastodon-scripts | ||||||
| This repo contains some scripts to help automate posting live MLB game updates | This repo contains some scripts to help automate posting live MLB game updates | ||||||
| to Mastodon. | to Mastodon. | ||||||
| 
 | 
 | ||||||
| ## Structure | ## Structure | ||||||
| * `mlbgames.sh`, `mlbstartpost.sh`, and `mlbfinalpost.sh` are used to make a |  | ||||||
|   post at the start and end of games. `mlbgames.sh` populates the `games` table |  | ||||||
| of a SQLite database, which the play and highlight scripts read when running, |  | ||||||
| so you'll need to run `mlbgames.sh` if you want to use the other scripts. |  | ||||||
| * `mlbplaysave.sh` and `mlbplaypost.sh` are used in tandem to post scoring | * `mlbplaysave.sh` and `mlbplaypost.sh` are used in tandem to post scoring | ||||||
|   updates. The former saves new plays to a SQLite database, and the latter |   updates. The former saves new plays to a SQLite database, and the latter | ||||||
| reads the database and posts any scoring plays that have not been posted yet. | reads the database and posts any scoring plays that have not been posted yet. | ||||||
| * `mlbhighlightsave.sh` and `mlbhighlightpost.sh` work similarly to the | * `mlbhighlightsave.sh` and `mlbhighlightpost.sh` work similarly to the | ||||||
|   previous pair, but post video highlights instead. |   previous pair, but post video highlights instead. | ||||||
| * `posthighlights.sql`, `postplays.sql`, `poststart.sql`, and `postfinal.sql` | * `posthighlights.sql` and `postplays.sql` are used by the posting scripts to | ||||||
|   are used by the posting scripts to select unposted data and then mark that |   select unposted data and then mark that data as posted. | ||||||
| data as posted. |  | ||||||
| * `schema.sql` defines the table that new data gets saved to. | * `schema.sql` defines the table that new data gets saved to. | ||||||
| 
 | 
 | ||||||
| Note that there are invisible characters (like `0x1f`) in some of the files. | Note that there are invisible characters (like 0x1f) in some of the files. | ||||||
| These are used as delimiters when processing the data. | These are used as delimiters when processing the data. | ||||||
| 
 | 
 | ||||||
| ## How to use | ## How to use | ||||||
| ### Dependencies | ### Dependencies | ||||||
| To run these scripts, you'll need a few other programs: | To run these scripts, you'll need a few other programs: | ||||||
| * [mlbstats](https://scm.dairydemon.net/filifa/mlbstats), another program I wrote | * [mlblive](https://scm.dairydemon.net/filifa/mlblive), another program I wrote | ||||||
|   for retrieving data from MLB's Stats API |   for retrieving data from MLB's Stats API | ||||||
| * `toot`, a CLI program for Mastodon | * `toot`, a CLI program for Mastodon | ||||||
| * `jq`, a program for processing JSON data | * `jq`, a program for processing JSON data | ||||||
|  | @ -38,16 +33,12 @@ scripts: | ||||||
| 1. Put all the shell scripts somewhere convenient (I put them in | 1. Put all the shell scripts somewhere convenient (I put them in | ||||||
|    `~/.local/bin`). |    `~/.local/bin`). | ||||||
| 2. Pick a directory to keep the SQLite database in (I picked | 2. Pick a directory to keep the SQLite database in (I picked | ||||||
|    `~/.local/share/mlbstats`). |    `~/.local/share/mlblive`). | ||||||
| 3. Create the database with `sqlite3 <filename> < schema.sql` | 3. Create the database with `sqlite3 <filename> < schema.sql` | ||||||
|     * I recommend making one database for plays and one for highlights |     * Note that if you intend to run both scripts, or run multiple instances of | ||||||
|       (assuming you wish to process both) for each team you want to follow. |       the scripts, you'll probably want to make a database for each instance. | ||||||
| Separate databases for plays and highlights will reduce delays due to locking. |  | ||||||
| 4. Put the other SQL scripts in the same directory as the database. | 4. Put the other SQL scripts in the same directory as the database. | ||||||
| 5. Review the shell scripts to see what arguments they require. | 5. Review the scripts to see what arguments they require. | ||||||
| 6. Write some systemd services and timers to execute the shell scripts | 6. Write some systemd services and timers to execute the shell scripts | ||||||
|    automatically on some interval. Set the working directory to the directory |    automatically on some interval. Set the working directory to the directory | ||||||
| with the database. | with the database. | ||||||
|     * I have `mlbsaveplays.sh` running as a simple service, and all the other |  | ||||||
|       scripts (games, highlights, `mlbpostplays.sh`) running as oneshot |  | ||||||
| services that execute on timers. |  | ||||||
|  |  | ||||||
|  | @ -1,35 +0,0 @@ | ||||||
| #!/bin/bash |  | ||||||
| 
 |  | ||||||
| set -e |  | ||||||
| 
 |  | ||||||
| while getopts 'd:a:' opt |  | ||||||
| do |  | ||||||
| 	case $opt in |  | ||||||
| 		d) |  | ||||||
| 			db=$OPTARG |  | ||||||
| 			;; |  | ||||||
| 		a) |  | ||||||
| 			account=$OPTARG |  | ||||||
| 			;; |  | ||||||
| 		?) |  | ||||||
| 			exit 1 |  | ||||||
| 			;; |  | ||||||
| 	esac |  | ||||||
| done |  | ||||||
| 
 |  | ||||||
| if [[ -z $db ]] |  | ||||||
| then |  | ||||||
| 	echo "$0:" '-d is required' >&2 |  | ||||||
| 	exit 1 |  | ||||||
| fi |  | ||||||
| 
 |  | ||||||
| if [[ -z $account ]] |  | ||||||
| then |  | ||||||
| 	echo "$0:" '-a is required' >&2 |  | ||||||
| 	exit 1 |  | ||||||
| fi |  | ||||||
| 
 |  | ||||||
| # format each row of data retreived from the select statement and pipe to toot |  | ||||||
| fmt='printf "Final:\n\n%s %s\n%s %s\n\n#baseball #live\n", $1, $2, $3, $4' |  | ||||||
| post="\"toot post --using $account\"" |  | ||||||
| sqlite3 $db < postfinal.sql | awk -F  "{$fmt | $post; close($post)}" |  | ||||||
|  | @ -1,35 +0,0 @@ | ||||||
| #!/bin/bash |  | ||||||
| 
 |  | ||||||
| set -e |  | ||||||
| 
 |  | ||||||
| while getopts 'd:a:' opt |  | ||||||
| do |  | ||||||
| 	case $opt in |  | ||||||
| 		d) |  | ||||||
| 			db=$OPTARG |  | ||||||
| 			;; |  | ||||||
| 		a) |  | ||||||
| 			account=$OPTARG |  | ||||||
| 			;; |  | ||||||
| 		?) |  | ||||||
| 			exit 1 |  | ||||||
| 			;; |  | ||||||
| 	esac |  | ||||||
| done |  | ||||||
| 
 |  | ||||||
| if [[ -z $db ]] |  | ||||||
| then |  | ||||||
| 	echo "$0:" '-d is required' >&2 |  | ||||||
| 	exit 1 |  | ||||||
| fi |  | ||||||
| 
 |  | ||||||
| if [[ -z $account ]] |  | ||||||
| then |  | ||||||
| 	echo "$0:" '-a is required' >&2 |  | ||||||
| 	exit 1 |  | ||||||
| fi |  | ||||||
| 
 |  | ||||||
| # format each row of data retreived from the select statement and pipe to toot |  | ||||||
| fmt='printf "Starting soon:\n\n%s (%s-%s) @\n%s (%s-%s)\n\n#baseball #live\n", $1, $2, $3, $4, $5, $6' |  | ||||||
| post="\"toot post --using $account\"" |  | ||||||
| sqlite3 $db < poststart.sql | awk -F  "{$fmt | $post; close($post)}" |  | ||||||
|  | @ -1,19 +0,0 @@ | ||||||
| .separator  |  | ||||||
| 
 |  | ||||||
| begin; |  | ||||||
| select |  | ||||||
| json ->> 'teams' ->> 'away' ->> 'team' ->> 'name', |  | ||||||
| json ->> 'teams' ->> 'away' ->> 'score', |  | ||||||
| json ->> 'teams' ->> 'home' ->> 'team' ->> 'name', |  | ||||||
| json ->> 'teams' ->> 'home' ->> 'score' |  | ||||||
| from games |  | ||||||
| where |  | ||||||
| json ->> 'status' ->> 'detailedState' = 'Final' and |  | ||||||
| posted = 0; |  | ||||||
| 
 |  | ||||||
| update games |  | ||||||
| set posted = 1 |  | ||||||
| where |  | ||||||
| json ->> 'status' ->> 'detailedState' = 'Final' and |  | ||||||
| posted = 0; |  | ||||||
| commit; |  | ||||||
|  | @ -1,21 +0,0 @@ | ||||||
| .separator  |  | ||||||
| 
 |  | ||||||
| begin; |  | ||||||
| select |  | ||||||
| json ->> 'teams' ->> 'away' ->> 'team' ->> 'name', |  | ||||||
| json ->> 'teams' ->> 'away' ->> 'leagueRecord' ->> 'wins', |  | ||||||
| json ->> 'teams' ->> 'away' ->> 'leagueRecord' ->> 'losses', |  | ||||||
| json ->> 'teams' ->> 'home' ->> 'team' ->> 'name', |  | ||||||
| json ->> 'teams' ->> 'home' ->> 'leagueRecord' ->> 'wins', |  | ||||||
| json ->> 'teams' ->> 'home' ->> 'leagueRecord' ->> 'losses' |  | ||||||
| from games |  | ||||||
| where |  | ||||||
| json ->> 'status' ->> 'detailedState' = 'Warmup' and |  | ||||||
| posted = 0; |  | ||||||
| 
 |  | ||||||
| update games |  | ||||||
| set posted = 1 |  | ||||||
| where |  | ||||||
| json ->> 'status' ->> 'detailedState' = 'Warmup' and |  | ||||||
| posted = 0; |  | ||||||
| commit; |  | ||||||
|  | @ -29,7 +29,7 @@ then | ||||||
| 	exit 1 | 	exit 1 | ||||||
| fi | fi | ||||||
| 
 | 
 | ||||||
| jqFilter='.dates[].games[]' | jqFilter='.dates[].games[] | "\(.gamePk),\(.status.detailedState)"' | ||||||
| fmt="OFS=\"\"; print \$0, 0" | fmt="OFS=\",\"; print \$1, \"$team\", \$2" | ||||||
| save="\"sqlite3 $db '.mode ascii' '.separator ' '.import /dev/stdin games'\"" | save="\"sqlite3 $db '.import --csv /dev/stdin games'\"" | ||||||
| mlbstats schedule -t $team | jq -Sc "$jqFilter" | awk -F  "{$fmt | $save; close($save)}" | mlblive schedule -t $team | jq -r "$jqFilter" | awk -F , "{$fmt | $save; close($save)}" | ||||||
|  | @ -9,6 +9,9 @@ do | ||||||
| 		d) | 		d) | ||||||
| 			db=$OPTARG | 			db=$OPTARG | ||||||
| 			;; | 			;; | ||||||
|  | 		t) | ||||||
|  | 			team=$OPTARG | ||||||
|  | 			;; | ||||||
| 		?) | 		?) | ||||||
| 			exit 1 | 			exit 1 | ||||||
| 			;; | 			;; | ||||||
|  | @ -21,10 +24,17 @@ then | ||||||
| 	exit 1 | 	exit 1 | ||||||
| fi | fi | ||||||
| 
 | 
 | ||||||
|  | if [[ -z $team ]] | ||||||
|  | then | ||||||
|  | 	echo "$0:" '-t is required' >&2 | ||||||
|  | 	exit 1 | ||||||
|  | fi | ||||||
|  | 
 | ||||||
| # grab the game pk of a single live game, if multiple (in case of spring | # grab the game pk of a single live game, if multiple (in case of spring | ||||||
| # training) | # training) | ||||||
| gamePk=$(sqlite3 $db "select json ->> 'gamePk' from games where json ->> 'status' ->> 'detailedState' in ('In Progress', 'Game Over') limit 1") | gamePk=$(sqlite3 $db "select gamePk from games where teamId='$team' and state='In Progress' limit 1") | ||||||
| if [[ -z "$gamePk" ]] | if [[ -z "$gamePk" ]] | ||||||
|  | if [[ "$gamePk" = 'null' ]] | ||||||
| then | then | ||||||
| 	echo "$0:" 'no live games found' >&2 | 	echo "$0:" 'no live games found' >&2 | ||||||
| 	exit 1 | 	exit 1 | ||||||
|  | @ -33,4 +43,4 @@ fi | ||||||
| # grab select data from response with jq, add 0 to the end of each line (to go | # grab select data from response with jq, add 0 to the end of each line (to go | ||||||
| # in the 'posted' db column), then write each line to db | # in the 'posted' db column), then write each line to db | ||||||
| jqFilter='.highlights.highlights.items | map(select(.keywordsAll[].value == "highlight"))[] | {headline, id} + {url: (.playbacks | map(select(.name == "mp4Avc"))[0].url)} | select(.url | startswith("https://mlb-cuts-diamond"))' | jqFilter='.highlights.highlights.items | map(select(.keywordsAll[].value == "highlight"))[] | {headline, id} + {url: (.playbacks | map(select(.name == "mp4Avc"))[0].url)} | select(.url | startswith("https://mlb-cuts-diamond"))' | ||||||
| mlbstats content -g $gamePk | jq -Sc "$jqFilter" | sed 's/$/0/' | sqlite3 $db '.mode ascii' '.separator  \n' '.import /dev/stdin highlights' | mlblive content -g $gamePk | jq -Sc "$jqFilter" | sed 's/$/0/' | sqlite3 $db '.mode ascii' '.separator  \n' '.import /dev/stdin highlights' | ||||||
|  | @ -3,12 +3,15 @@ | ||||||
| set -e | set -e | ||||||
| set -o pipefail | set -o pipefail | ||||||
| 
 | 
 | ||||||
| while getopts 'd:' opt | while getopts 'd:t:' opt | ||||||
| do | do | ||||||
| 	case $opt in | 	case $opt in | ||||||
| 		d) | 		d) | ||||||
| 			db=$OPTARG | 			db=$OPTARG | ||||||
| 			;; | 			;; | ||||||
|  | 		t) | ||||||
|  | 			team=$OPTARG | ||||||
|  | 			;; | ||||||
| 		?) | 		?) | ||||||
| 			exit 1 | 			exit 1 | ||||||
| 			;; | 			;; | ||||||
|  | @ -21,9 +24,15 @@ then | ||||||
| 	exit 1 | 	exit 1 | ||||||
| fi | fi | ||||||
| 
 | 
 | ||||||
|  | if [[ -z $team ]] | ||||||
|  | then | ||||||
|  | 	echo "$0:" '-t is required' >&2 | ||||||
|  | 	exit 1 | ||||||
|  | fi | ||||||
|  | 
 | ||||||
| # grab the game pk of a single live game, if multiple (in case of spring | # grab the game pk of a single live game, if multiple (in case of spring | ||||||
| # training) | # training) | ||||||
| gamePk=$(sqlite3 $db "select json ->> 'gamePk' from games where json ->> 'status' ->> 'detailedState' in ('In Progress', 'Warmup') limit 1") | gamePk=$(sqlite3 $db "select gamePk from games where teamId='$team' and state in ('In Progress', 'Warmup') limit 1") | ||||||
| if [[ -z "$gamePk" ]] | if [[ -z "$gamePk" ]] | ||||||
| then | then | ||||||
| 	echo "$0:" 'no live games found' >&2 | 	echo "$0:" 'no live games found' >&2 | ||||||
|  | @ -37,4 +46,4 @@ save="\"sqlite3 $db '.mode ascii' '.separator ' '.import /dev/stdin plays'\"" | ||||||
| 
 | 
 | ||||||
| # grab select data from each response with jq, add 0 to the end of each line | # grab select data from each response with jq, add 0 to the end of each line | ||||||
| # (to go in the 'posted' db column), then write each line as they come in to db | # (to go in the 'posted' db column), then write each line as they come in to db | ||||||
| mlbstats subscribe -g $gamePk | jq -Sc "$jqFilter" | awk "{$fmt | $save; close($save)}" | mlblive subscribe -g $gamePk | jq -Sc "$jqFilter" | awk "{$fmt | $save; close($save)}" | ||||||
|  | @ -5,9 +5,9 @@ select | ||||||
| json ->> 'description',  | json ->> 'description',  | ||||||
| json ->> 'halfInning', | json ->> 'halfInning', | ||||||
| json ->> 'inning', | json ->> 'inning', | ||||||
| replace(json ->> 'awayTeam', ' ', ''), | json ->> 'awayTeam', | ||||||
| json ->> 'awayScore', | json ->> 'awayScore', | ||||||
| replace(json ->> 'homeTeam', ' ', ''), | json ->> 'homeTeam', | ||||||
| json ->> 'homeScore' | json ->> 'homeScore' | ||||||
| from plays | from plays | ||||||
| where | where | ||||||
							
								
								
									
										18
									
								
								schema.sql
								
								
								
								
							
							
						
						
									
										18
									
								
								schema.sql
								
								
								
								
							|  | @ -17,18 +17,8 @@ on highlights(posted) | ||||||
| where posted = 0; | where posted = 0; | ||||||
| 
 | 
 | ||||||
| create table if not exists games ( | create table if not exists games ( | ||||||
| 	"json" text unique on conflict ignore, | 	gamePk	integer, | ||||||
| 	"posted" integer check ("posted" in (0, 1)) | 	teamId	text, | ||||||
|  | 	state	text, | ||||||
|  | 	unique (teamId, gamePk) on conflict replace | ||||||
| ); | ); | ||||||
| 
 |  | ||||||
| create index if not exists nonposted_games |  | ||||||
| on games(posted) |  | ||||||
| where posted = 0; |  | ||||||
| 
 |  | ||||||
| create trigger if not exists delete_old_states |  | ||||||
| before insert on games |  | ||||||
| begin |  | ||||||
|        delete from games |  | ||||||
|        where json ->> 'gamePk' = new.json ->> 'gamePk' and |  | ||||||
|        json ->> 'status' ->> 'detailedState' != new.json ->> 'status' ->> 'detailedState'; |  | ||||||
| end; |  | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue