Skip to content

Commit 4c89def

Browse files
more JF support, allow skip tmbd
1 parent b5a403c commit 4c89def

11 files changed

+203
-40
lines changed

Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM golang:1.21 as build
1+
FROM golang:1.22 as build
22

33
WORKDIR /go/src/app
44
COPY . .

Dockerfile.dev

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
FROM golang:1.22 as build
2+
3+
WORKDIR /go/src/app
4+
COPY . .
5+
RUN go mod download
6+
WORKDIR /go/src/app/cmd
7+
8+
RUN CGO_ENABLED=0 go build -o /go/bin/app
9+
10+
FROM alpine:20231219
11+
12+
RUN apk add supervisor
13+
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
14+
COPY docker/watch.py /watch.py
15+
16+
COPY --from=build /go/bin/app /
17+
COPY --from=build /go/src/app/web /web
18+
EXPOSE 9999
19+
20+
# CMD ["/app"]
21+
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ build:
55
test:
66
./test.sh
77
docker-build:
8-
docker buildx build --load --tag gowatchit-local .
8+
docker buildx build --platform linux/amd64 --load --tag gowatchit-local . -f ./Dockerfile.dev
99
docker-push:
1010
docker buildx build --push --platform linux/amd64 --tag ghcr.io/iloveicedgreentea/gowatchit:test .
1111
docker-run:
12-
LOG_FILE=false LOG_LEVEL=debug docker-compose up
12+
LOG_FILE=false LOG_LEVEL=debug docker-compose -f docker-compose-test.yml up
1313
run: build
1414
LOG_FILE=false LOG_LEVEL=debug ./build/server

docker-compose-test.yml

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
version: '3.8'
2+
3+
services:
4+
plex-webhook-automation:
5+
platform: linux/amd64
6+
image: gowatchit-local:latest
7+
ports:
8+
- '9999:9999'
9+
environment:
10+
SUPER_DEBUG: 'false'
11+
LOG_LEVEL: 'debug'
12+
volumes:
13+
- ./docker/data:/data
14+
- ./web:/web

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/iloveicedgreentea/go-plex
22

3-
go 1.21
3+
go 1.22
44

55
require (
66
github.com/anaskhan96/soup v1.2.5

internal/handlers/jellyfin_handler.go

+127-18
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@ func ProcessJfWebhook(jfChan chan<- models.JellyfinWebhook, c *gin.Context) {
2727
read, err := io.ReadAll(r)
2828
if err != nil {
2929
log.Errorf("Error reading request body: %v", err)
30+
return
3031
}
3132

32-
// log.Debugf("ProcessJfWebhook Request: %v", string(read))
3333
var payload models.JellyfinWebhook
3434
err = json.Unmarshal(read, &payload)
3535
if err != nil {
3636
log.Errorf("Error decoding payload: %v", err)
37+
log.Debugf("ProcessJfWebhook Request: %v", string(read))
38+
return
3739
}
3840
log.Debugf("Payload: %#v", payload)
3941
// respond to request with 200
@@ -48,7 +50,7 @@ func jfEventRouter(jfClient *jellyfin.JellyfinClient, beqClient *ezbeq.BeqClient
4850
clientUUID := payload.ClientName
4951
// ensure the client matches so it doesnt trigger from unwanted clients
5052

51-
if !checkUUID(clientUUID, config.GetString("plex.deviceUUIDFilter")) {
53+
if !checkUUID(clientUUID, config.GetString("jellyfin.deviceUUIDFilter")) {
5254
log.Infof("Got a webhook but Client UUID '%s' does not match enabled filter", clientUUID)
5355
return
5456
}
@@ -61,6 +63,7 @@ func jfEventRouter(jfClient *jellyfin.JellyfinClient, beqClient *ezbeq.BeqClient
6163
metadata, err := jfClient.GetMetadata(payload.UserID, payload.ItemID)
6264
if err != nil {
6365
log.Errorf("Error getting metadata from jellyfin API: %v", err)
66+
return
6467
}
6568

6669
log.Debugf("Processing media type: %s", metadata.Type)
@@ -116,26 +119,25 @@ func jfEventRouter(jfClient *jellyfin.JellyfinClient, beqClient *ezbeq.BeqClient
116119
switch payload.NotificationType {
117120
// unload BEQ on pause OR stop because I never press stop, just pause and then back.
118121
case "PlaybackStart":
119-
log.Debug("Event Router: media.play received")
122+
// TODO: test start/resume/pause
120123
jfMediaPlay(jfClient, beqClient, haClient, payload, model, false, data, skipActions)
121124
case "PlaybackStop":
122-
log.Debug("Event Router: media.stop received")
123125
jfMediaStop(jfClient, beqClient, haClient, payload, model, false, data, skipActions)
124126
// really annoyingly jellyfin doesnt send a pause or resume event only progress every X seconds with a isPaused flag
125127
// TODO: support pause resume without running resume on every playbackprogress
126-
// case "PlaybackProgress":
127-
// log.Debug("Event Router: PlaybackProgress received")
128-
// if payload.IsPaused == "true" {
129-
// mediaPause(beqClient, haClient, payload, model, skipActions)
130-
// } else {
131-
// mediaResume()
132-
// }
128+
case "PlaybackProgress":
129+
if payload.IsPaused == "true" {
130+
jfMediaPause(beqClient, haClient, payload, model, skipActions)
131+
} else {
132+
jfMediaResume(jfClient, beqClient, haClient, payload, model, false, data, skipActions)
133+
}
133134
default:
134135
log.Warnf("Received unsupported webhook event. Nothing to do: %s", payload.NotificationType)
135136
}
136137
}
137138

138139
func jfMediaPlay(client *jellyfin.JellyfinClient, beqClient *ezbeq.BeqClient, haClient *homeassistant.HomeAssistantClient, payload models.JellyfinWebhook, m *models.SearchRequest, useDenonCodec bool, data models.JellyfinMetadata, skipActions *bool) {
140+
log.Debug("Processing media play event")
139141
wg := &sync.WaitGroup{}
140142

141143
// stop processing webhooks
@@ -172,8 +174,12 @@ func jfMediaPlay(client *jellyfin.JellyfinClient, beqClient *ezbeq.BeqClient, ha
172174

173175
m.TMDB, err = client.GetJfTMDB(data)
174176
if err != nil {
175-
log.Errorf("Error getting TMDB data from metadata: %v", err)
176-
return
177+
if config.GetBool("jellyfin.skiptmdb") {
178+
log.Warn("TMDB data not found. TMDB is allowed to be skipped")
179+
} else {
180+
log.Errorf("Error getting TMDB data from metadata: %v", err)
181+
return
182+
}
177183
}
178184
err = beqClient.LoadBeqProfile(m)
179185
if err != nil {
@@ -196,15 +202,118 @@ func jfMediaPlay(client *jellyfin.JellyfinClient, beqClient *ezbeq.BeqClient, ha
196202
}
197203

198204
func jfMediaStop(client *jellyfin.JellyfinClient, beqClient *ezbeq.BeqClient, haClient *homeassistant.HomeAssistantClient, payload models.JellyfinWebhook, m *models.SearchRequest, useDenonCodec bool, data models.JellyfinMetadata, skipActions *bool) {
199-
return
200-
// TODO: implement
205+
log.Debug("Processing media stop event")
206+
err := mqtt.PublishWrapper(config.GetString("mqtt.topicplayingstatus"), "false")
207+
if err != nil {
208+
log.Error(err)
209+
}
210+
go common.ChangeLight("on")
211+
212+
err = beqClient.UnloadBeqProfile(m)
213+
if err != nil {
214+
log.Error(err)
215+
if config.GetBool("ezbeq.notifyOnLoad") && config.GetBool("homeAssistant.enabled") {
216+
err := haClient.SendNotification(fmt.Sprintf("Error UNLOADING profile: %v -- Unsafe to play movies!", err))
217+
if err != nil {
218+
log.Error()
219+
}
220+
}
221+
}
222+
log.Info("BEQ profile unloaded")
223+
}
224+
225+
func jfMediaPause(beqClient *ezbeq.BeqClient, haClient *homeassistant.HomeAssistantClient, payload models.JellyfinWebhook, m *models.SearchRequest, skipActions *bool) {
226+
log.Debug("Processing media pause event")
227+
if !*skipActions {
228+
err := mqtt.PublishWrapper(config.GetString("mqtt.topicplayingstatus"), "false")
229+
if err != nil {
230+
log.Error(err)
231+
}
232+
233+
go common.ChangeLight("on")
234+
235+
err = beqClient.UnloadBeqProfile(m)
236+
if err != nil {
237+
log.Error(err)
238+
if config.GetBool("ezbeq.notifyOnLoad") && config.GetBool("homeAssistant.enabled") {
239+
err := haClient.SendNotification(fmt.Sprintf("Error UNLOADING profile: %v -- Unsafe to play movies!", err))
240+
if err != nil {
241+
log.Error()
242+
}
243+
}
244+
}
245+
log.Info("BEQ profile unloaded")
246+
}
247+
}
248+
func jfMediaResume(client *jellyfin.JellyfinClient, beqClient *ezbeq.BeqClient, haClient *homeassistant.HomeAssistantClient, payload models.JellyfinWebhook, m *models.SearchRequest, useDenonCodec bool, data models.JellyfinMetadata, skipActions *bool) {
249+
log.Debug("Processing media resume event")
250+
if !*skipActions {
251+
// mediaType string, codec string, edition string
252+
// trigger lights
253+
err := mqtt.PublishWrapper(config.GetString("mqtt.topicplayingstatus"), "true")
254+
if err != nil {
255+
log.Error(err)
256+
}
257+
go common.ChangeLight("off")
258+
// Changing on resume is disabled because its annoying if you changed it since playing
259+
// go changeMasterVolume(vip, mediaType)
260+
261+
// allow skipping search to save time
262+
// always unload in case something is loaded from movie for tv
263+
err = beqClient.UnloadBeqProfile(m)
264+
if err != nil {
265+
log.Errorf("Error on startup - unloading beq %v", err)
266+
}
267+
if data.Type == showItemTitle {
268+
if !config.GetBool("ezbeq.enableTvBeq") {
269+
return
270+
}
271+
}
272+
// get the tmdb id to match with ezbeq catalog
273+
m.TMDB, err = client.GetJfTMDB(data)
274+
if err != nil {
275+
log.Errorf("Error getting TMDB data from metadata: %v", err)
276+
return
277+
}
278+
// if the server was restarted, cached data is lost
279+
if m.Codec == "" {
280+
log.Warn("No codec found in cache on resume. Was server restarted? Getting new codec")
281+
log.Debug("Using jellyfin to get codec because its not cached")
282+
m.Codec, err = client.GetAudioCodec(data)
283+
if err != nil {
284+
log.Errorf("error getting codec from jellyfin, can't continue: %s", err)
285+
return
286+
}
287+
}
288+
if m.Codec == "" {
289+
log.Error("No codec found after trying to resume")
290+
return
291+
}
292+
293+
err = beqClient.LoadBeqProfile(m)
294+
if err != nil {
295+
log.Error(err)
296+
return
297+
}
298+
log.Info("BEQ profile loaded")
299+
300+
// send notification of it loaded
301+
if config.GetBool("ezbeq.notifyOnLoad") && config.GetBool("homeAssistant.enabled") {
302+
err := haClient.SendNotification(fmt.Sprintf("BEQ Profile: Title - %s (%s) // Codec %s", data.OriginalTitle, payload.Year, m.Codec))
303+
if err != nil {
304+
log.Error()
305+
}
306+
}
307+
}
201308
}
202309

203310
// // entry point for background tasks
204311
func JellyfinWorker(jfChan <-chan models.JellyfinWebhook, readyChan chan<- bool) {
205-
readyChan <- true
206-
207-
log.Info("JellyfinWorker started")
312+
if !config.GetBool("jellyfin.enabled") {
313+
log.Debug("Jellyfin is disabled")
314+
readyChan <- true
315+
return
316+
}
208317

209318
// Server Info
210319
jellyfinClient := jellyfin.NewClient(config.GetString("jellyfin.url"), config.GetString("jellyfin.port"), config.GetString("jellyfin.playerMachineIdentifier"), config.GetString("jellyfin.playerIP"))

internal/handlers/plex_handler.go

+5
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,11 @@ func getPlexMovieDb(payload models.PlexWebhookPayload) string {
486486

487487
// entry point for background tasks
488488
func PlexWorker(plexChan <-chan models.PlexWebhookPayload, readyChan chan<- bool) {
489+
if !config.GetBool("plex.enabled") {
490+
log.Debug("Plex is disabled")
491+
readyChan <- true
492+
return
493+
}
489494
log.Info("PlexWorker started")
490495

491496
var beqClient *ezbeq.BeqClient

internal/jellyfin/jellyfin.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,6 @@ func (c *JellyfinClient) makeRequest(endpoint string, method string) (io.ReadClo
9595
return resp.Body, err
9696
}
9797

98-
// TODO: is paused
99-
10098
// GetMetadata returns the metadata for a given itemID
10199
func (c *JellyfinClient) GetMetadata(userID, itemID string) (metadata models.JellyfinMetadata, err error) {
102100
// take the itemID and get the codec
@@ -175,6 +173,7 @@ func (c *JellyfinClient) GetEdition(payload models.JellyfinMetadata) (edition st
175173
// GetJfTMDB extracts the tmdb id of a given itemID because its not returned directly in the metadata for some reason
176174
func (c *JellyfinClient) GetJfTMDB(payload models.JellyfinMetadata) (string, error) {
177175
urls := payload.ExternalUrls
176+
log.Debugf("External urls: %#v", urls)
178177
for _, u := range urls {
179178
if u.Name == "TheMovieDb" {
180179
s := strings.Replace(u.URL, "https://www.themoviedb.org/", "", -1)

readme.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ You must use the [official Jellyfin Webhooks plugin](https://github.com/jellyfin
104104
4) You can optionally add a user filter
105105
5) Item types: Movies, Episodes
106106

107-
Configure the webhook in whatever way you want but it *must* include the following:
107+
Configure the webhook in whatever way you want but it *must* include the following and in this order:
108108

109109
```json
110110
{
@@ -115,13 +115,13 @@ Configure the webhook in whatever way you want but it *must* include the followi
115115
"ItemId": "{{ItemId}}",
116116
"ItemType": "{{ItemType}}",
117117
"NotificationType": "{{NotificationType}}",
118-
"Year": "{{Year}}",
119118
{{#if_equals NotificationType 'PlaybackStop'}}
120-
"PlayedToCompletion": "{{PlayedToCompletion}}"
119+
"PlayedToCompletion": "{{PlayedToCompletion}}",
121120
{{/if_equals}}
122121
{{#if_equals NotificationType 'PlaybackProgress'}}
123-
"IsPaused": "{{IsPaused}}"
122+
"IsPaused": "{{IsPaused}}",
124123
{{/if_equals}}
124+
"Year": "{{Year}}"
125125
}
126126
```
127127
#### Generate API Key

web/app.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,14 @@ function populateFields(config) {
5555
document.getElementById('plex-enabletrailersupport').checked = config.plex.enabletrailersupport;
5656
// jellyfin
5757
document.getElementById('jellyfin-enabled').checked = config.jellyfin.enabled;
58+
document.getElementById('jellyfin-skiptmdb').checked = config.jellyfin.skiptmdb;
5859
document.getElementById('jellyfin-url').value = config.jellyfin.url;
5960
document.getElementById('jellyfin-port').value = config.jellyfin.port;
6061
document.getElementById('jellyfin-ownernamefilter').value = config.jellyfin.ownernamefilter;
6162
document.getElementById('jellyfin-deviceuuidfilter').value = config.jellyfin.deviceuuidfilter;
6263
document.getElementById('jellyfin-playermachineidentifier').value = config.jellyfin.playermachineidentifier;
63-
document.getElementById('jellyfin-userID').value = config.jellyfin.userID;
64-
document.getElementById('jellyfin-apiToken').value = config.jellyfin.apiToken;
64+
document.getElementById('jellyfin-userid').value = config.jellyfin.userid;
65+
document.getElementById('jellyfin-apitoken').value = config.jellyfin.apitoken;
6566

6667
// Signal
6768
document.getElementById('signal-enabled').checked = config.signal.enabled;
@@ -128,13 +129,14 @@ function buildFinalConfig() {
128129
};
129130
const jellyfinConfig = {
130131
"enabled": document.getElementById('jellyfin-enabled').checked,
132+
"skiptmdb": document.getElementById('jellyfin-skiptmdb').checked,
131133
"url": document.getElementById('jellyfin-url').value,
132134
"port": document.getElementById('jellyfin-port').value,
133135
"ownernamefilter": document.getElementById('jellyfin-ownernamefilter').value,
134136
"deviceuuidfilter": document.getElementById('jellyfin-deviceuuidfilter').value,
135137
"playermachineidentifier": document.getElementById('jellyfin-playermachineidentifier').value,
136-
"userID": document.getElementById('jellyfin-userID').value,
137-
"apiToken": document.getElementById('jellyfin-apiToken').value
138+
"userid": document.getElementById('jellyfin-userid').value,
139+
"apitoken": document.getElementById('jellyfin-apitoken').value
138140
};
139141
const signalConfig = {
140142
"enabled": document.getElementById('signal-enabled').checked,

0 commit comments

Comments
 (0)