Skip to content

Commit de1753d

Browse files
committed
Merge branch 'dev-primetime43' into main
2 parents 19e49e3 + 0dca1a7 commit de1753d

File tree

10 files changed

+226
-73
lines changed

10 files changed

+226
-73
lines changed

Dockerfile

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Use an official Python runtime as a parent image
2+
FROM python:3.7-slim-buster
3+
4+
# Set the working directory in the container to /app
5+
WORKDIR /app
6+
7+
# Add the current directory contents into the container at /app
8+
ADD . /app
9+
10+
# Update and install build dependencies
11+
RUN apt-get update && apt-get install -y \
12+
build-essential \
13+
libssl-dev \
14+
libffi-dev \
15+
python3-dev \
16+
python3-pip
17+
18+
# Install any needed packages specified in requirements.txt
19+
RUN pip install --no-cache-dir -r requirements.txt
20+
21+
# Make port 80 available to the world outside this container
22+
EXPOSE 5000
23+
24+
# Define environment variable
25+
ENV NAME World
26+
27+
# Run app.py when the container launches
28+
CMD ["uwsgi", "--ini", "app.ini"]
29+

GAPS 2.py

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -78,40 +78,39 @@ def save_tmdb_key():
7878
# If the API key is not valid, return an error message
7979
return {'message': app.config['RESPONSE_MESSAGES']['api_key_failure'] + str(response.status_code)}, response.status_code
8080

81-
@app.route('/link_plex_account', methods=['POST'])
82-
def link_plex_account():
83-
try:
84-
headers = {'X-Plex-Client-Identifier': app.config['PLEX_CLIENT_IDENTIFIER']}
85-
pinlogin = MyPlexPinLogin(headers=headers, oauth=True)
86-
oauth_url = pinlogin.oauthUrl()
87-
webbrowser.open(oauth_url)
88-
pinlogin.run(timeout=120)
89-
pinlogin.waitForLogin()
90-
if pinlogin.token:
91-
plex_data = PlexAccountData() # Create a new PlexAccountData object
92-
plex_account = MyPlexAccount(token=pinlogin.token)
93-
username = plex_account.username # Get the username
94-
resources = [resource for resource in plex_account.resources() if resource.owned and resource.connections]
95-
servers = [f"{resource.name} ({resource.connections[0].address})" for resource in resources if resource.connections]
96-
97-
# Store tokens in the dictionary
98-
for resource in resources:
99-
if resource.connections:
100-
server_name = f"{resource.name} ({resource.connections[0].address})"
101-
tokens[server_name] = pinlogin.token
102-
plex_data.add_token(server_name, pinlogin.token)
103-
104-
plex_data.set_servers(servers)
105-
106-
# Store the PlexAccountData object in the array
107-
plex_data_array.append(plex_data)
108-
109-
# Return the JSON response with servers and token
110-
return jsonify(servers=servers, token=pinlogin.token)
111-
else:
112-
return jsonify({'message': app.config['RESPONSE_MESSAGES']['plex_account_error'], 'servers': [], 'token': None})
113-
except Exception as e:
114-
return jsonify({'message': app.config['RESPONSE_MESSAGES']['plex_account_error'], 'servers': [], 'token': None})
81+
@app.route('/fetch_servers', methods=['POST'])
82+
def fetch_servers():
83+
if globalPin.checkLogin(): # Assumes that checkLogin() returns True if the user is authenticated
84+
plex_data = PlexAccountData() # Create a new PlexAccountData object
85+
plex_account = MyPlexAccount(token=globalPin.token)
86+
username = plex_account.username # Get the username
87+
resources = [resource for resource in plex_account.resources() if resource.owned and resource.connections]
88+
servers = [f"{resource.name} ({resource.connections[0].address})" for resource in resources if resource.connections]
89+
90+
# Store tokens in the dictionary
91+
for resource in resources:
92+
if resource.connections:
93+
server_name = f"{resource.name} ({resource.connections[0].address})"
94+
tokens[server_name] = globalPin.token
95+
plex_data.add_token(server_name, globalPin.token)
96+
97+
plex_data.set_servers(servers)
98+
99+
# Store the PlexAccountData object in the array
100+
plex_data_array.append(plex_data)
101+
102+
# Return the JSON response with servers and token
103+
return jsonify(servers=servers, token=globalPin.token)
104+
else:
105+
return jsonify({'message': 'User is not authenticated', 'servers': [], 'token': None})
106+
107+
globalPin = None
108+
@app.route('/authenticate_plex_acc', methods=['POST'])
109+
def authenticate_plex_acc():
110+
global globalPin
111+
globalPin = MyPlexPinLogin(oauth=True)
112+
oauth_url = globalPin.oauthUrl()
113+
return jsonify({'oauth_url': oauth_url})
115114

116115
@app.route('/fetch_libraries/<serverName>')
117116
def fetch_libraries(serverName):
@@ -324,4 +323,4 @@ def get_recommendated_movies():
324323
moviesFromSelectedLibrary = {}
325324

326325
if __name__ == '__main__':
327-
app.run(debug=False)
326+
app.run(host='0.0.0.0', port=5000, debug=False)

app.ini

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[uwsgi]
2+
module = wsgi:app
3+
http = :5000
4+
master = true
5+
processes = 5
6+
7+
socket = app.sock
8+
chmod-socket = 660
9+
vacuum = true
10+
11+
die-on-term = true

config.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
# Define the base URLs
1313
TMDB_BASE_URL = "https://api.themoviedb.org/3"
1414
TMDB_IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w500"
15-
PLEX_CLIENT_IDENTIFIER = "your_unique_client_identifier"
1615

1716
# Define the response messages
1817
RESPONSE_MESSAGES = {

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
Flask
22
requests
3-
plexapi
3+
plexapi
4+
uwsgi

templates/about.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
33

44
<head>
5-
<title>Gaps</title>
5+
<title>Gaps 2</title>
66
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
77
<link href="{{ url_for('static', filename='images/gaps.ico') }}" rel="shortcut icon"/>
88
<link href="{{ url_for('static', filename='css/libraries/bootstrap.min.css') }}" rel="stylesheet"/>
@@ -22,10 +22,10 @@
2222
<h3 class="top-margin">About</h3>
2323

2424
<p class="text-muted">
25-
GAPS 2 is a rewrite of the original <a href="https://github.com/JasonHHouse/gaps" target="_blank">GAPS project</a>, now written in Python instead of Java. GAPS (Gaps A Plex Server) finds movies you're missing in your Plex Server. It's a great way to find additional movies that you might be interested in based on collections from movies in your Plex Server.
25+
<a href="https://github.com/primetime43/GAPS-2" target="_blank">GAPS 2</a> is a rewrite of the original <a href="https://github.com/JasonHHouse/gaps" target="_blank">GAPS project</a>, now written in Python instead of Java. GAPS (Gaps A Plex Server) finds movies you're missing in your Plex Server. It's a great way to find additional movies that you might be interested in based on collections from movies in your Plex Server.
2626
</p>
2727
<p class="text-muted">
28-
The GAPS 2 project aims to bring the same functionality with the simplicity and versatility of Python.
28+
The <a href="https://github.com/primetime43/GAPS-2" target="_blank">GAPS 2</a> project aims to bring the same functionality with the simplicity and versatility of Python.
2929
</p>
3030

3131
<p class="text-muted">Gaps searches through your Plex Server. It then queries

templates/configuration.html

Lines changed: 126 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,15 @@ <h3 class="top-margin">Settings</h3>
2828
<li class="nav-item">
2929
<a class="nav-link" data-toggle="tab" href="#plex" id="plexTab">Plex</a>
3030
</li>
31+
<li class="nav-item">
32+
<a class="nav-link" data-toggle="tab" href="#jellyfin" id="jellyfinTab">Jellyfin</a>
33+
</li>
34+
<li class="nav-item">
35+
<a class="nav-link" data-toggle="tab" href="#emby" id="embyTab">Emby</a>
36+
</li>
3137
</ul>
3238
<div class="tab-content top-margin" id="myTabContent">
39+
<!-- TMdb tab -->
3340
<div class="tab-pane fade show active top-margin" id="tmdb">
3441
<p>To use Gaps, you'll need a MovieDB api key. Navigate over to <a
3542
href="https://www.themoviedb.org/settings/api" rel="noopener noreferrer" target="_blank">The
@@ -148,13 +155,18 @@ <h3 class="top-margin">Settings</h3>
148155

149156
</form>
150157
</div>
158+
<!-- Plex tab -->
151159
<div class="tab-pane fade top-margin" id="plex">
152160
<form class="needs-validation" id="plexConfiguration" novalidate>
153161

154162
<div class="form-group mt-3">
155-
<button class="btn btn-primary" type="button" id="linkPlexAccountBtn">Link Plex Account</button>
156-
</div>
157-
163+
<div>
164+
<button class="btn btn-primary" type="button" id="authPlexAccountBtn">Authenticate Plex Account</button>
165+
</div>
166+
<div class="mt-3">
167+
<button id="fetchServersBtn" class="btn btn-primary" type="button">Fetch Servers</button>
168+
</div>
169+
</div>
158170

159171
<div class="form-group">
160172
<label for="server">Plex Server</label>
@@ -191,6 +203,41 @@ <h3 class="top-margin">Servers</h3>
191203
<div id="activeServerInfo"></div>
192204
</form>
193205
</div>
206+
<!-- Jellyfin tab -->
207+
<div class="tab-pane fade top-margin" id="jellyfin">
208+
<form class="needs-validation" id="jellyfinConfiguration" novalidate>
209+
<div class="form-group">
210+
<label for="jellyfinServer">Jellyfin Server Address</label>
211+
<input class="form-control" id="jellyfinServer" required type="text">
212+
<div class="invalid-feedback">
213+
Please enter your Jellyfin server address.
214+
</div>
215+
</div>
216+
<div class="form-group">
217+
<label for="jellyfinUsername">Jellyfin Username</label>
218+
<input class="form-control" id="jellyfinUsername" required type="text">
219+
<div class="invalid-feedback">
220+
Please enter your Jellyfin username.
221+
</div>
222+
</div>
223+
<div class="form-group">
224+
<label for="jellyfinPassword">Jellyfin Password</label>
225+
<div class="input-group">
226+
<input class="form-control" id="jellyfinPassword" required type="password">
227+
<div class="input-group-append">
228+
<button id="toggleJellyfinPasswordVisibility" class="btn btn-outline-secondary" type="button">Show</button>
229+
</div>
230+
</div>
231+
<div class="invalid-feedback">
232+
Please enter your Jellyfin password.
233+
</div>
234+
</div>
235+
<button class="btn btn-primary" type="submit">Save</button>
236+
</form>
237+
</div>
238+
<!-- Emby tab -->
239+
<div class="tab-pane fade top-margin" id="emby">
240+
</div>
194241
</div>
195242

196243
<div th:insert="fragments/common :: contextPath"></div>
@@ -222,37 +269,19 @@ <h4 class="alert-heading">Error!</h4>
222269
</div>
223270

224271
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
272+
273+
<!-- Authenticates the user's account -->
225274
<script>
226275
$(document).ready(function () {
227-
$('#linkPlexAccountBtn').on('click', function () {
228-
// Show the loading text/spinner
229-
$('#loading').show();
230-
231-
$.post('/link_plex_account', function (response) {
232-
// Check if there's a message in the response
233-
if (response.message) {
234-
// Show an alert with the message
235-
alert(response.message);
236-
} else {
237-
// Update the dropdown options with the server data
238-
var servers = response.servers;
239-
var dropdown = $('#server');
240-
dropdown.empty();
241-
servers.forEach(function (server) {
242-
dropdown.append($('<option>').text(server).attr('value', server));
243-
});
244-
245-
// Set the Plex token
246-
var token = response.token;
247-
$('#plexToken').val(token);
248-
}
249-
250-
// Hide the loading text/spinner
251-
$('#loading').hide();
276+
$('#authPlexAccountBtn').on('click', function () {
277+
// Send a POST request to the /authenticate_plex_acc endpoint
278+
$.post('/authenticate_plex_acc', function (response) {
279+
// Open the OAuth URL in a new tab
280+
window.open(response.oauth_url, '_blank');
252281
});
253282
});
254283
});
255-
</script>
284+
</script>
256285

257286
<script>
258287
$('#togglePlexTokenVisibility').on('click', function () {
@@ -267,6 +296,33 @@ <h4 class="alert-heading">Error!</h4>
267296
});
268297
</script>
269298

299+
<!-- Fetches the servers after the user has been authenticated -->
300+
<script>
301+
$('#fetchServersBtn').on('click', function () {
302+
// Show the loading text/spinner
303+
$('#loading').show();
304+
$.post('/fetch_servers', function (response) {
305+
if (response.message) {
306+
alert(response.message);
307+
} else {
308+
// Update the dropdown options with the server data
309+
var servers = response.servers;
310+
var dropdown = $('#server');
311+
dropdown.empty();
312+
servers.forEach(function (server) {
313+
dropdown.append($('<option>').text(server).attr('value', server));
314+
});
315+
316+
// Set the Plex token
317+
var token = response.token;
318+
$('#plexToken').val(token);
319+
}
320+
// Hide the loading text/spinner
321+
$('#loading').hide();
322+
});
323+
});
324+
</script>
325+
270326
<script>
271327
function displayActiveServerInfo(activeServer, libraries) {
272328
var activeServerInfo = document.getElementById('activeServerInfo');
@@ -293,6 +349,10 @@ <h4 class="alert-heading">Error!</h4>
293349

294350
// Function to be called on page load
295351
window.addEventListener('load', function () {
352+
// Hide the Jellyfin & emby tab
353+
$('#jellyfinTab').hide();
354+
$('#embyTab').hide();
355+
296356
// Send an AJAX request to get the active server data from the Python side
297357
$.ajax({
298358
url: '/get_active_server',
@@ -345,7 +405,7 @@ <h4 class="alert-heading">Error!</h4>
345405

346406
// Hide the loading text/spinner
347407
$('#saving').hide();
348-
408+
showActiveServerData(event);
349409
},
350410
error: function (error) {
351411
// Handle error response here
@@ -394,6 +454,42 @@ <h4 class="alert-heading">Error!</h4>
394454

395455
</script>
396456

457+
<!-- Jellyfin Scripts -->
458+
<script>
459+
$('#jellyfinConfiguration').on('submit', function (event) {
460+
event.preventDefault();
461+
462+
var server = $('#jellyfinServer').val();
463+
var username = $('#jellyfinUsername').val();
464+
var password = $('#jellyfinPassword').val();
465+
466+
$.ajax({
467+
url: '/save_jellyfin_data',
468+
type: 'POST',
469+
contentType: 'application/json',
470+
data: JSON.stringify({ server: server, username: username, password: password }),
471+
success: function (response) {
472+
console.log('Jellyfin data saved successfully');
473+
},
474+
error: function (error) {
475+
console.log('Failed to save Jellyfin data');
476+
}
477+
});
478+
});
479+
480+
// Show/hide the password button
481+
$('#toggleJellyfinPasswordVisibility').on('click', function () {
482+
var jellyfinPasswordInput = $('#jellyfinPassword');
483+
if (jellyfinPasswordInput.attr('type') === 'password') {
484+
jellyfinPasswordInput.attr('type', 'text');
485+
$(this).text('Hide');
486+
} else {
487+
jellyfinPasswordInput.attr('type', 'password');
488+
$(this).text('Show');
489+
}
490+
});
491+
</script>
492+
397493
<script src="{{ url_for('static', filename='js/libraries/jquery-3.4.1.min.js') }}"
398494
type="text/javascript"></script>
399495
<script src="{{ url_for('static', filename='js/libraries/bootstrap.bundle.min.js') }}"

0 commit comments

Comments
 (0)