Podcast scrobbling
This commit is contained in:
7
Rakefile
7
Rakefile
@@ -30,6 +30,13 @@ namespace :blog do
|
||||
end
|
||||
end
|
||||
|
||||
task scrobble_podcasts: ["blog:load_environment"] do
|
||||
require "hanami/prepare"
|
||||
|
||||
command = Adamantium::OvercastScrobbler.new(username: "daniel@dnitza.com", password: "impacted-mingle.buckeye4incise")
|
||||
command.()
|
||||
end
|
||||
|
||||
task load_from_bookshelf: ["blog:load_environment"] do
|
||||
require "hanami/prepare"
|
||||
require "csv"
|
||||
|
@@ -1082,6 +1082,10 @@ video {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.mb-1 {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.mb-12 {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
@@ -1114,10 +1118,6 @@ video {
|
||||
margin-left: 0.375rem;
|
||||
}
|
||||
|
||||
.ml-2 {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.ml-\[5\] {
|
||||
margin-left: 5;
|
||||
}
|
||||
@@ -1278,10 +1278,6 @@ video {
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.w-64 {
|
||||
width: 16rem;
|
||||
}
|
||||
|
||||
.max-w-3xl {
|
||||
max-width: 48rem;
|
||||
}
|
||||
@@ -1302,6 +1298,10 @@ video {
|
||||
flex: 1 1 0%;
|
||||
}
|
||||
|
||||
.flex-auto {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.flex-initial {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
@@ -1310,22 +1310,10 @@ video {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.flex-auto {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.basis-1\/4 {
|
||||
flex-basis: 25%;
|
||||
}
|
||||
|
||||
.basis-full {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
.basis-auto {
|
||||
flex-basis: auto;
|
||||
}
|
||||
@@ -1365,6 +1353,10 @@ video {
|
||||
grid-auto-flow: row;
|
||||
}
|
||||
|
||||
.grid-flow-col {
|
||||
grid-auto-flow: column;
|
||||
}
|
||||
|
||||
.grid-cols-2 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
@@ -1389,6 +1381,10 @@ video {
|
||||
grid-template-rows: repeat(1, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.grid-rows-2 {
|
||||
grid-template-rows: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
@@ -1405,6 +1401,10 @@ video {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -1421,6 +1421,16 @@ video {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.gap-x-1 {
|
||||
-moz-column-gap: 0.25rem;
|
||||
column-gap: 0.25rem;
|
||||
}
|
||||
|
||||
.gap-x-1\.5 {
|
||||
-moz-column-gap: 0.375rem;
|
||||
column-gap: 0.375rem;
|
||||
}
|
||||
|
||||
.space-x-1 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-right: calc(0.25rem * var(--tw-space-x-reverse));
|
||||
@@ -1587,6 +1597,16 @@ video {
|
||||
background-color: rgb(224 231 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-indigo-300 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(165 180 252 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-indigo-50 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(238 242 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-orange-100 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 237 213 / var(--tw-bg-opacity));
|
||||
@@ -1617,28 +1637,14 @@ video {
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-yellow-100\/60 {
|
||||
background-color: rgb(254 249 195 / 0.6);
|
||||
.bg-pink-400 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(244 114 182 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-indigo-50 {
|
||||
.bg-pink-200 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(238 242 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-indigo-400 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(129 140 248 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-indigo-600 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(79 70 229 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-indigo-300 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(165 180 252 / var(--tw-bg-opacity));
|
||||
background-color: rgb(251 207 232 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-opacity-75 {
|
||||
@@ -1736,6 +1742,10 @@ video {
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
.pt-1 {
|
||||
padding-top: 0.25rem;
|
||||
}
|
||||
|
||||
.pt-2 {
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
@@ -1744,22 +1754,6 @@ video {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.pr-1 {
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
|
||||
.pr-4 {
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.pt-1 {
|
||||
padding-top: 0.25rem;
|
||||
}
|
||||
|
||||
.pt-1\.5 {
|
||||
padding-top: 0.375rem;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
@@ -1796,10 +1790,6 @@ video {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.text-xsm {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -1816,10 +1806,6 @@ video {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.leading-10 {
|
||||
line-height: 2.5rem;
|
||||
}
|
||||
|
||||
.leading-6 {
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
@@ -1903,11 +1889,21 @@ video {
|
||||
color: rgb(129 140 248 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-indigo-900 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(49 46 129 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-orange-100 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 237 213 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-pink-400 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(244 114 182 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-pink-600 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(219 39 119 / var(--tw-text-opacity));
|
||||
@@ -1928,14 +1924,14 @@ video {
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-indigo-50 {
|
||||
.text-pink-900 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(238 242 255 / var(--tw-text-opacity));
|
||||
color: rgb(131 24 67 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-indigo-900 {
|
||||
.text-pink-800 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(49 46 129 / var(--tw-text-opacity));
|
||||
color: rgb(157 23 77 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.underline {
|
||||
@@ -2172,11 +2168,6 @@ h1, h2, h3, h4, h5, h6, h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
||||
border-color: rgb(37 99 235 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.hover\:border-blue-800:hover {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(30 64 175 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-blue-100:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(219 234 254 / var(--tw-bg-opacity));
|
||||
@@ -2192,11 +2183,6 @@ h1, h2, h3, h4, h5, h6, h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
||||
background-color: rgb(147 197 253 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-blue-800:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(30 64 175 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-emerald-100:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(209 250 229 / var(--tw-bg-opacity));
|
||||
@@ -2237,11 +2223,6 @@ h1, h2, h3, h4, h5, h6, h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
||||
background-color: rgb(254 226 226 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-yellow-200:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(254 240 138 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:fill-blue-400:hover {
|
||||
fill: #60a5fa;
|
||||
}
|
||||
@@ -2254,11 +2235,6 @@ h1, h2, h3, h4, h5, h6, h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
||||
fill: #c084fc;
|
||||
}
|
||||
|
||||
.hover\:text-blue-100:hover {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(219 234 254 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.hover\:text-blue-400:hover {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(96 165 250 / var(--tw-text-opacity));
|
||||
@@ -2304,6 +2280,11 @@ h1, h2, h3, h4, h5, h6, h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
||||
color: rgb(244 114 182 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.hover\:text-pink-600:hover {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(219 39 119 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.hover\:text-red-400:hover {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(248 113 113 / var(--tw-text-opacity));
|
||||
@@ -2313,6 +2294,10 @@ h1, h2, h3, h4, h5, h6, h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.hover\:decoration-wavy:hover {
|
||||
text-decoration-style: wavy;
|
||||
}
|
||||
|
||||
.hover\:opacity-80:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
@@ -2463,10 +2448,6 @@ h1, h2, h3, h4, h5, h6, h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
||||
background-color: rgb(251 191 36 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-amber-400\/60 {
|
||||
background-color: rgb(251 191 36 / 0.6);
|
||||
}
|
||||
|
||||
.dark\:bg-black {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
|
||||
@@ -2506,6 +2487,11 @@ h1, h2, h3, h4, h5, h6, h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
||||
background-color: rgb(17 24 39 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-indigo-400 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(129 140 248 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-indigo-950 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(30 27 75 / var(--tw-bg-opacity));
|
||||
@@ -2531,21 +2517,6 @@ h1, h2, h3, h4, h5, h6, h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
||||
background-color: rgb(15 23 42 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-indigo-300 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(165 180 252 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-indigo-400 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(129 140 248 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:text-amber-100 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(254 243 199 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:text-amber-500 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(245 158 11 / var(--tw-text-opacity));
|
||||
@@ -2596,11 +2567,6 @@ h1, h2, h3, h4, h5, h6, h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
||||
color: rgb(75 85 99 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:text-gray-800 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(31 41 55 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:text-indigo-300 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(165 180 252 / var(--tw-text-opacity));
|
||||
@@ -2651,6 +2617,16 @@ h1, h2, h3, h4, h5, h6, h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:text-pink-200 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(251 207 232 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:text-pink-100 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(252 231 243 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:shadow-pink-200 {
|
||||
--tw-shadow-color: #fbcfe8;
|
||||
--tw-shadow: var(--tw-shadow-colored);
|
||||
@@ -2741,10 +2717,6 @@ h1, h2, h3, h4, h5, h6, h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
||||
border-color: rgb(15 23 42 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.dark\:hover\:bg-amber-400\/80:hover {
|
||||
background-color: rgb(251 191 36 / 0.8);
|
||||
}
|
||||
|
||||
.dark\:hover\:bg-emerald-800:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(6 95 70 / var(--tw-bg-opacity));
|
||||
@@ -2790,9 +2762,9 @@ h1, h2, h3, h4, h5, h6, h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
||||
background-color: rgb(131 24 67 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:hover\:text-yellow-100:hover {
|
||||
.dark\:hover\:text-pink-100:hover {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(254 249 195 / var(--tw-text-opacity));
|
||||
color: rgb(252 231 243 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.hover\:dark\:text-blue-100:hover {
|
||||
|
11
app/relations/podcast_scrobbles.rb
Normal file
11
app/relations/podcast_scrobbles.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Adamantium
|
||||
module Relations
|
||||
class PodcastScrobbles < ROM::Relation[:sql]
|
||||
schema :podcast_scrobbles, infer: true
|
||||
|
||||
auto_struct(true)
|
||||
end
|
||||
end
|
||||
end
|
20
app/repos/podcast_scrobble_repo.rb
Normal file
20
app/repos/podcast_scrobble_repo.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
module Adamantium
|
||||
module Repos
|
||||
class PodcastScrobbleRepo < Adamantium::Repo[:podcast_scrobbles]
|
||||
commands :create
|
||||
|
||||
def exists?(id:)
|
||||
!!podcast_scrobbles
|
||||
.where(overcast_id: id)
|
||||
.one
|
||||
end
|
||||
|
||||
def listing
|
||||
podcast_scrobbles
|
||||
.order(Sequel.desc(:listened_at))
|
||||
.limit(5)
|
||||
.to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@@ -4,6 +4,14 @@ div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:
|
||||
h1 🎙️ Podcasts
|
||||
|
||||
div class="mb-12 max-w-prose mx-auto"
|
||||
- if listens && listens.count > 0
|
||||
div class="p-4 bg-pink-200 dark:bg-pink-900 rounded"
|
||||
h4 class="p-0 m-0 text-pink-900 dark:text-pink-200" Recent listens
|
||||
- listens.each do |listen|
|
||||
div
|
||||
a class="text-pink-800 dark:text-pink-100 no-underline hover:decoration-wavy hover:underline" href=listen.url
|
||||
= "#{listen.podcast_name}: #{listen.title}"
|
||||
|
||||
table class="prose dark:prose-invert table-auto"
|
||||
thead
|
||||
tr
|
||||
|
@@ -2,11 +2,15 @@ module Adamantium
|
||||
module Views
|
||||
module Podcasts
|
||||
class Index < View
|
||||
include Deps["repos.podcast_repo"]
|
||||
include Deps["repos.podcast_repo", "repos.podcast_scrobble_repo"]
|
||||
|
||||
expose :podcasts do
|
||||
podcast_repo.listing
|
||||
end
|
||||
|
||||
expose :listens do
|
||||
podcast_scrobble_repo.listing
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
17
db/migrate/20231130095816_create_podcast_scrobbles.rb
Normal file
17
db/migrate/20231130095816_create_podcast_scrobbles.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
ROM::SQL.migration do
|
||||
change do
|
||||
create_table :podcast_scrobbles do
|
||||
primary_key :id
|
||||
column :overcast_id, :text
|
||||
column :podcast_name, :text
|
||||
column :title, :text
|
||||
column :url, :text
|
||||
column :enclosure_url, :text
|
||||
column :listened_at, :date
|
||||
|
||||
index :overcast_id
|
||||
end
|
||||
end
|
||||
end
|
46
lib/adamantium/overcast_scrobbler.rb
Normal file
46
lib/adamantium/overcast_scrobbler.rb
Normal file
@@ -0,0 +1,46 @@
|
||||
require "httparty"
|
||||
require "nokogiri"
|
||||
require "down"
|
||||
|
||||
module Adamantium
|
||||
class OvercastScrobbler
|
||||
def initialize(username:, password:)
|
||||
@username = username
|
||||
@password = password
|
||||
end
|
||||
|
||||
def call
|
||||
repo = Adamantium::Container["repos.podcast_scrobble_repo"]
|
||||
response = HTTParty.post("https://overcast.fm/login", body: {email: @username, password: @password})
|
||||
cookie = response.request.options[:headers]["Cookie"]
|
||||
|
||||
opml_file = Down::NetHttp.download("https://overcast.fm/account/export_opml/extended", headers: {"Cookie" => cookie})
|
||||
doc = File.open(opml_file) { |f| Nokogiri::XML(f) }
|
||||
|
||||
podcast_list = []
|
||||
|
||||
doc.xpath("//outline[@type='rss']").each_with_object(podcast_list) do |rss, memo|
|
||||
podcasts = rss.xpath("outline[@type='podcast-episode']").select{|ep| ep.get_attribute("played") == "1" }
|
||||
|
||||
name = rss.get_attribute("title")
|
||||
|
||||
podcasts.each do |episode|
|
||||
attrs = {}
|
||||
attrs[:overcast_id] = episode.get_attribute("overcastId")
|
||||
attrs[:podcast_name] = name
|
||||
attrs[:title] = episode.get_attribute("title")
|
||||
attrs[:url] = episode.get_attribute("url")
|
||||
attrs[:enclosure_url] = episode.get_attribute("enclosureUrl")
|
||||
attrs[:listened_at] = episode.get_attribute("userUpdatedDate")
|
||||
|
||||
memo << attrs
|
||||
end
|
||||
|
||||
podcast_list.each do |podcast|
|
||||
next if repo.exists?(id: podcast[:overcast_id])
|
||||
repo.create(podcast)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user