Compare commits
	
		
			21 Commits
		
	
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | a9fe0fbad6 | |
|  | 120604c131 | |
|  | 72aa69088a | |
|  | 6de414d359 | |
|  | 97b63bfeba | |
|  | 0a15e0586b | |
|  | 5e2cabd8b7 | |
|  | 318b048090 | |
|  | 5197766815 | |
|  | 9ecc9a358b | |
|  | c85c101b59 | |
|  | ac38f8f28d | |
|  | 7f1938c9e5 | |
|  | 62c429dafb | |
|  | ddf5d7f34f | |
|  | 8654bb13c3 | |
|  | 41d494eab4 | |
|  | 972a6226a2 | |
|  | a8f4bc5e30 | |
|  | 5015924d19 | |
|  | 02b63027e2 | 
							
								
								
									
										27
									
								
								README.md
								
								
								
								
							
							
						
						
									
										27
									
								
								README.md
								
								
								
								
							|  | @ -1,24 +1,29 @@ | |||
| # mlblive-mastodon-scripts | ||||
| # mlbstats-mastodon-scripts | ||||
| This repo contains some scripts to help automate posting live MLB game updates | ||||
| to Mastodon. | ||||
| 
 | ||||
| ## 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 | ||||
|   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. | ||||
| * `mlbhighlightsave.sh` and `mlbhighlightpost.sh` work similarly to the | ||||
|   previous pair, but post video highlights instead. | ||||
| * `posthighlights.sql` and `postplays.sql` are used by the posting scripts to | ||||
|   select unposted data and then mark that data as posted. | ||||
| * `posthighlights.sql`, `postplays.sql`, `poststart.sql`, and `postfinal.sql` | ||||
|   are used by the posting scripts to select unposted data and then mark that | ||||
| data as posted. | ||||
| * `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. | ||||
| 
 | ||||
| ## How to use | ||||
| ### Dependencies | ||||
| To run these scripts, you'll need a few other programs: | ||||
| * [mlblive](https://scm.dairydemon.net/filifa/mlblive), another program I wrote | ||||
| * [mlbstats](https://scm.dairydemon.net/filifa/mlbstats), another program I wrote | ||||
|   for retrieving data from MLB's Stats API | ||||
| * `toot`, a CLI program for Mastodon | ||||
| * `jq`, a program for processing JSON data | ||||
|  | @ -33,12 +38,16 @@ scripts: | |||
| 1. Put all the shell scripts somewhere convenient (I put them in | ||||
|    `~/.local/bin`). | ||||
| 2. Pick a directory to keep the SQLite database in (I picked | ||||
|    `~/.local/share/mlblive`). | ||||
|    `~/.local/share/mlbstats`). | ||||
| 3. Create the database with `sqlite3 <filename> < schema.sql` | ||||
|     * Note that if you intend to run both scripts, or run multiple instances of | ||||
|       the scripts, you'll probably want to make a database for each instance. | ||||
|     * I recommend making one database for plays and one for highlights | ||||
|       (assuming you wish to process both) for each team you want to follow. | ||||
| 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. | ||||
| 5. Review the scripts to see what arguments they require. | ||||
| 5. Review the shell scripts to see what arguments they require. | ||||
| 6. Write some systemd services and timers to execute the shell scripts | ||||
|    automatically on some interval. Set the working directory to the directory | ||||
| 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. | ||||
|  |  | |||
|  | @ -0,0 +1,35 @@ | |||
| #!/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)}" | ||||
|  | @ -29,7 +29,7 @@ then | |||
| 	exit 1 | ||||
| fi | ||||
| 
 | ||||
| jqFilter='.dates[].games[] | "\(.gamePk),\(.status.detailedState)"' | ||||
| fmt="OFS=\",\"; print \$1, \"$team\", \$2" | ||||
| save="\"sqlite3 $db '.import --csv /dev/stdin games'\"" | ||||
| mlblive schedule -t $team | jq -r "$jqFilter" | awk -F , "{$fmt | $save; close($save)}" | ||||
| jqFilter='.dates[].games[]' | ||||
| fmt="OFS=\"\"; print \$0, 0" | ||||
| save="\"sqlite3 $db '.mode ascii' '.separator ' '.import /dev/stdin games'\"" | ||||
| mlbstats schedule -t $team | jq -Sc "$jqFilter" | awk -F  "{$fmt | $save; close($save)}" | ||||
|  | @ -0,0 +1,35 @@ | |||
| #!/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)}" | ||||
|  | @ -0,0 +1,19 @@ | |||
| .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; | ||||
|  | @ -0,0 +1,21 @@ | |||
| .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; | ||||
|  | @ -9,9 +9,6 @@ do | |||
| 		d) | ||||
| 			db=$OPTARG | ||||
| 			;; | ||||
| 		t) | ||||
| 			team=$OPTARG | ||||
| 			;; | ||||
| 		?) | ||||
| 			exit 1 | ||||
| 			;; | ||||
|  | @ -24,17 +21,10 @@ then | |||
| 	exit 1 | ||||
| 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 | ||||
| # training) | ||||
| gamePk=$(sqlite3 $db "select gamePk from games where teamId='$team' and state='In Progress' limit 1") | ||||
| gamePk=$(sqlite3 $db "select json ->> 'gamePk' from games where json ->> 'status' ->> 'detailedState' in ('In Progress', 'Game Over') limit 1") | ||||
| if [[ -z "$gamePk" ]] | ||||
| if [[ "$gamePk" = 'null' ]] | ||||
| then | ||||
| 	echo "$0:" 'no live games found' >&2 | ||||
| 	exit 1 | ||||
|  | @ -43,4 +33,4 @@ fi | |||
| # 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 | ||||
| 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"))' | ||||
| mlblive content -g $gamePk | jq -Sc "$jqFilter" | sed 's/$/0/' | sqlite3 $db '.mode ascii' '.separator  \n' '.import /dev/stdin highlights' | ||||
| mlbstats content -g $gamePk | jq -Sc "$jqFilter" | sed 's/$/0/' | sqlite3 $db '.mode ascii' '.separator  \n' '.import /dev/stdin highlights' | ||||
|  | @ -3,15 +3,12 @@ | |||
| set -e | ||||
| set -o pipefail | ||||
| 
 | ||||
| while getopts 'd:t:' opt | ||||
| while getopts 'd:' opt | ||||
| do | ||||
| 	case $opt in | ||||
| 		d) | ||||
| 			db=$OPTARG | ||||
| 			;; | ||||
| 		t) | ||||
| 			team=$OPTARG | ||||
| 			;; | ||||
| 		?) | ||||
| 			exit 1 | ||||
| 			;; | ||||
|  | @ -24,15 +21,9 @@ then | |||
| 	exit 1 | ||||
| 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 | ||||
| # training) | ||||
| gamePk=$(sqlite3 $db "select gamePk from games where teamId='$team' and state in ('In Progress', 'Warmup') limit 1") | ||||
| gamePk=$(sqlite3 $db "select json ->> 'gamePk' from games where json ->> 'status' ->> 'detailedState' in ('In Progress', 'Warmup') limit 1") | ||||
| if [[ -z "$gamePk" ]] | ||||
| then | ||||
| 	echo "$0:" 'no live games found' >&2 | ||||
|  | @ -46,4 +37,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 | ||||
| # (to go in the 'posted' db column), then write each line as they come in to db | ||||
| mlblive subscribe -g $gamePk | jq -Sc "$jqFilter" | awk "{$fmt | $save; close($save)}" | ||||
| mlbstats subscribe -g $gamePk | jq -Sc "$jqFilter" | awk "{$fmt | $save; close($save)}" | ||||
|  | @ -5,9 +5,9 @@ select | |||
| json ->> 'description',  | ||||
| json ->> 'halfInning', | ||||
| json ->> 'inning', | ||||
| json ->> 'awayTeam', | ||||
| replace(json ->> 'awayTeam', ' ', ''), | ||||
| json ->> 'awayScore', | ||||
| json ->> 'homeTeam', | ||||
| replace(json ->> 'homeTeam', ' ', ''), | ||||
| json ->> 'homeScore' | ||||
| from plays | ||||
| where | ||||
							
								
								
									
										18
									
								
								schema.sql
								
								
								
								
							
							
						
						
									
										18
									
								
								schema.sql
								
								
								
								
							|  | @ -17,8 +17,18 @@ on highlights(posted) | |||
| where posted = 0; | ||||
| 
 | ||||
| create table if not exists games ( | ||||
| 	gamePk	integer, | ||||
| 	teamId	text, | ||||
| 	state	text, | ||||
| 	unique (teamId, gamePk) on conflict replace | ||||
| 	"json" text unique on conflict ignore, | ||||
| 	"posted" integer check ("posted" in (0, 1)) | ||||
| ); | ||||
| 
 | ||||
| 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