feat: ♻️ Remake url #3
@ -1 +1,2 @@
|
||||
.env
|
||||
n8n_workflow.json
|
@ -1,4 +1,5 @@
|
||||
client_id=
|
||||
client_secret=
|
||||
n8n_webhook=
|
||||
enviroment=dev
|
||||
n8n_webhook_add_tracks=
|
||||
n8n_webhook_create_playlist=
|
||||
environment=production
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -28,6 +28,8 @@
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
.idea/
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
|
8
.idea/.gitignore
generated
vendored
8
.idea/.gitignore
generated
vendored
@ -1,8 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
12
.idea/dataSources.xml
generated
12
.idea/dataSources.xml
generated
@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="database.db" uuid="bc21304a-7893-4f0b-bc20-7e9bb19b7991">
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||
<jdbc-url>jdbc:sqlite:database.db</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
10
.idea/inspectionProfiles/Project_Default.xml
generated
10
.idea/inspectionProfiles/Project_Default.xml
generated
@ -1,10 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<Languages>
|
||||
<language minSize="63" name="Python" />
|
||||
</Languages>
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
6
.idea/inspectionProfiles/profiles_settings.xml
generated
6
.idea/inspectionProfiles/profiles_settings.xml
generated
@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
6
.idea/jsLibraryMappings.xml
generated
6
.idea/jsLibraryMappings.xml
generated
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptLibraryMappings">
|
||||
<file url="file://$PROJECT_DIR$" libraries="{sweetalert2}" />
|
||||
</component>
|
||||
</project>
|
7
.idea/misc.xml
generated
7
.idea/misc.xml
generated
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.12 (playlistCreator)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (playlistCreator)" project-jdk-type="Python SDK" />
|
||||
</project>
|
8
.idea/modules.xml
generated
8
.idea/modules.xml
generated
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/playlistCreator.iml" filepath="$PROJECT_DIR$/.idea/playlistCreator.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
11
.idea/playlistCreator.iml
generated
11
.idea/playlistCreator.iml
generated
@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="sweetalert2" level="application" />
|
||||
</component>
|
||||
</module>
|
7
.idea/sqldialects.xml
generated
7
.idea/sqldialects.xml
generated
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SqlDialectMappings">
|
||||
<file url="file://$PROJECT_DIR$/main.py" dialect="GenericSQL" />
|
||||
<file url="PROJECT" dialect="SQLite" />
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
@ -1,5 +1,7 @@
|
||||
FROM python:3.11-alpine
|
||||
LABEL authors="louisgallet"
|
||||
LABEL authors="Louis Gallet"
|
||||
LABEL description="Make This Playlist is a web application that allows anyone to add music to a Spotify playlist without needing a Spotify account. The application is accessible at the following address: [Make This Playlist](https://makethisplaylist.louisgallet.fr)."
|
||||
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE 1
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
11
README.md
11
README.md
@ -7,16 +7,24 @@ Make This Playlist is a web application that allows anyone to add music to a Spo
|
||||
- Search for music on Spotify
|
||||
- Add music to a playlist
|
||||
|
||||
## Requirements
|
||||
- n8n account or instance
|
||||
- n8n workflow (import [n8n workflow file](https://gitea.louisgallet.fr/lgallet/makethisplaylist/src/branch/main/n8n_workflow.json) into your account and set your Spotify credentials)
|
||||
- Spotify Developer Account
|
||||
|
||||
## Installation
|
||||
|
||||
### Docker
|
||||
You can install the application via Docker. To do this, execute the following command:
|
||||
For the webhook URLs, make sure to remove the parameters, as they will be added directly by the application.
|
||||
```bash
|
||||
docker run -d \
|
||||
-p 80:3000 \
|
||||
-e client_id=SPOTIFY_CLIENT_ID \
|
||||
-e client_secret=SPOTIFY_CLIENT_SECRET \
|
||||
-e n8n_webhook=N8N_WEBHOOK_URL \
|
||||
-e n8n_webhook_create_playlist=N8N_WEBHOOK_URL \
|
||||
-e n8n_webhook_add_tracks=N8N_WEBHOOK_URL \
|
||||
-e environment=production \
|
||||
gitea.louisgallet.fr/lgallet/makethisplaylist:latest
|
||||
```
|
||||
|
||||
@ -26,5 +34,6 @@ To install the application manually, clone the repository and install the depend
|
||||
git clone https://gitea.louisgallet.fr/lgallet/makethisplaylist.git
|
||||
cd makethisplaylist
|
||||
pip install -r requirements.txt
|
||||
cp .env.example .env
|
||||
python main.py
|
||||
```
|
9
main.py
9
main.py
@ -41,7 +41,6 @@ def create_playlist():
|
||||
data = requests.get((os.getenv("n8n_webhook_create_playlist") + "/" + str(roomid) + "/" + playlistName))
|
||||
playlistID = data.json()['playlistID']
|
||||
spotifyURL = data.json()['spotifyURL']
|
||||
print(playlistID)
|
||||
cursor.execute("INSERT INTO rooms (roomid, spotify_id, playlist_name, spotify_URL) VALUES (?, ?, ?, ?)",
|
||||
(roomid, playlistID, playlistName, spotifyURL))
|
||||
database.commit()
|
||||
@ -54,7 +53,7 @@ def create_playlist():
|
||||
return render_template("create.html", response='Request failed', comment=e, type="error")
|
||||
|
||||
@app.route('/search/<string:roomid>', methods=['GET', 'POST'])
|
||||
def main_app(roomid):
|
||||
def search(roomid):
|
||||
if len(roomid) != 8 or not cursor.execute("SELECT * FROM rooms WHERE roomid = ?", (roomid,)).fetchone():
|
||||
return main()
|
||||
playlistName = cursor.execute("SELECT playlist_name FROM rooms WHERE roomid = ?", (roomid,)).fetchone()[0]
|
||||
@ -69,16 +68,12 @@ def main_app(roomid):
|
||||
# otherwise handle the GET request
|
||||
return render_template("search.html", roomid=roomid, playlistName=playlistName, spotifyURL=spotifyURL)
|
||||
|
||||
@app.route('/add/<string:roomid>/<string:playlistID>/<string:trackid>', methods=['GET'])
|
||||
@app.route('/add/room/<string:roomid>/playlist/<string:playlistID>/track/<string:trackid>', methods=['GET'])
|
||||
def add_to_playlist(roomid, playlistID, trackid):
|
||||
print(roomid)
|
||||
print(playlistID)
|
||||
print(trackid)
|
||||
if not os.getenv("n8n_webhook_add_tracks"):
|
||||
return render_template("add.html", response="No n8n webhook provided", comment="Please provide a n8n webhook in the .env file", type="error", roomid=roomid)
|
||||
try:
|
||||
data = requests.get(os.getenv("n8n_webhook_add_tracks") + "/" + playlistID + "/" + trackid)
|
||||
print(data.json)
|
||||
if data.json()['status'] == 'success':
|
||||
return render_template("add.html", response="Track added to playlist successfully", comment="Enjoy the night \U0001f57a", type="success", roomid=roomid)
|
||||
|
||||
|
207
n8n_workflow.json
Normal file
207
n8n_workflow.json
Normal file
@ -0,0 +1,207 @@
|
||||
{
|
||||
"name": "PlaylistCreator Workflows",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"content": "## Workflow to add track to a playlist\n\nThis webhook is called with the following parameters: `playlistid` and `trackid`, which are sent by the Flask application. This webhook will attempt to add the desired track to the playlist. We assume that the playlist exists and that the current user has the right to add a track to it. As for the `trackid`, it is guaranteed to be valid since it is retrieved from the Spotify API.",
|
||||
"height": 212,
|
||||
"width": 495
|
||||
},
|
||||
"id": "1c6779f6-ed75-457e-b19c-dd24268eb527",
|
||||
"name": "Sticky Note",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
460,
|
||||
120
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "playlist",
|
||||
"id": "={{ $json.params.playlistid }}",
|
||||
"trackID": "={{ $('Add Track Webhook').item.json.params.trackid }}",
|
||||
"additionalFields": {}
|
||||
},
|
||||
"id": "6defaf1d-1c28-46d2-b31f-55b02cd8dda3",
|
||||
"name": "Add track to the Spotify Playlist",
|
||||
"type": "n8n-nodes-base.spotify",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
680,
|
||||
320
|
||||
],
|
||||
"alwaysOutputData": true,
|
||||
"credentials": {
|
||||
"spotifyOAuth2Api": {
|
||||
"id": "ry4GegWcWUTXupsE",
|
||||
"name": "Spotify account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "playlist",
|
||||
"operation": "create",
|
||||
"name": "={{ $json.params.playlistName }} - {{ $json.params.roomID }}",
|
||||
"additionalFields": {
|
||||
"description": "=Playlist created with Make This Playlist - https://makethisplaylist.louisgallet.fr. Add some music by entering the roomID: {{ $json.params.roomID }}",
|
||||
"public": true
|
||||
}
|
||||
},
|
||||
"id": "319efc18-903e-42d2-bb72-ce50b8bef1ee",
|
||||
"name": "Create a Spotify playlist",
|
||||
"type": "n8n-nodes-base.spotify",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
660,
|
||||
740
|
||||
],
|
||||
"credentials": {
|
||||
"spotifyOAuth2Api": {
|
||||
"id": "ry4GegWcWUTXupsE",
|
||||
"name": "Spotify account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"content": "## Workflow to create a Spotify playlist\n\nThis webhook is called with the following parameters: `roomid` and `playlistid`, which are sent by the Flask application. This webhook will attempt to create the playlist on the associated Spotify account. We assume that the playlist name will be unique, as it uses the following format: `playlist name` - `roomid`.",
|
||||
"height": 212,
|
||||
"width": 495
|
||||
},
|
||||
"id": "972e1642-69f9-4458-b252-35b8446f4a15",
|
||||
"name": "Sticky Note1",
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
480,
|
||||
560
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={\n \"playlistID\": \"{{ $json.id }}\", \n \"spotifyURL\": \"{{ $json.external_urls.spotify }}\"\n} ",
|
||||
"options": {}
|
||||
},
|
||||
"id": "4223f8bf-0c4d-4184-8a50-bb3e9f1bb1fb",
|
||||
"name": "Respond to Webhook",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
840,
|
||||
740
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "{\n \"status\": \"success\"\n}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "296a009b-cfb2-4594-bedc-7362b5f0e7eb",
|
||||
"name": "Respond to Webhook1",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
860,
|
||||
320
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"path": "61f08b8c-5bca-4091-a934-da66d9ae09e7/:playlistid/:trackid",
|
||||
"responseMode": "responseNode",
|
||||
"options": {}
|
||||
},
|
||||
"id": "de91df67-33f9-4a6a-aaa4-17a272d54968",
|
||||
"name": "Add Track Webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
500,
|
||||
320
|
||||
],
|
||||
"webhookId": "61f08b8c-5bca-4091-a934-da66d9ae09e7",
|
||||
"notesInFlow": true
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"path": "b1447737-cf34-43d5-8d42-8fb29fbfa275/:roomID/:playlistName",
|
||||
"responseMode": "responseNode",
|
||||
"options": {}
|
||||
},
|
||||
"id": "2b8e9c76-786e-4784-aa8c-0789727b375b",
|
||||
"name": "Create Playlist Webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
480,
|
||||
740
|
||||
],
|
||||
"webhookId": "b1447737-cf34-43d5-8d42-8fb29fbfa275"
|
||||
}
|
||||
],
|
||||
"pinData": {},
|
||||
"connections": {
|
||||
"Create a Spotify playlist": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Respond to Webhook",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Add track to the Spotify Playlist": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Respond to Webhook1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Add Track Webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Add track to the Spotify Playlist",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Create Playlist Webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Create a Spotify playlist",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": true,
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"timezone": "Europe/Paris",
|
||||
"saveManualExecutions": true,
|
||||
"callerPolicy": "workflowsFromSameOwner"
|
||||
},
|
||||
"versionId": "21bcacb5-a3ce-4355-ba98-bb3c865e2905",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "2ebecfc6e81edd91c6988c37863cd68c7d8e130f8ce8a9ab9fe59ab24a8d3949"
|
||||
},
|
||||
"id": "kWG5BTxflXVL7fFm",
|
||||
"tags": []
|
||||
}
|
@ -1,64 +1,6 @@
|
||||
appnope==0.1.4
|
||||
asttokens==2.4.1
|
||||
attrs==23.2.0
|
||||
backcall==0.2.0
|
||||
beautifulsoup4==4.12.3
|
||||
bleach==6.1.0
|
||||
blinker==1.8.2
|
||||
Bootstrap-Flask==2.4.0
|
||||
certifi==2024.6.2
|
||||
charset-normalizer==3.3.2
|
||||
click==8.1.7
|
||||
decorator==5.1.1
|
||||
defusedxml==0.7.1
|
||||
docopt==0.6.2
|
||||
executing==2.0.1
|
||||
fastjsonschema==2.20.0
|
||||
Flask==3.0.3
|
||||
idna==3.7
|
||||
ipython==8.12.3
|
||||
itsdangerous==2.2.0
|
||||
jedi==0.19.1
|
||||
Jinja2==3.1.4
|
||||
jsonschema==4.22.0
|
||||
jsonschema-specifications==2023.12.1
|
||||
jupyter_client==8.6.2
|
||||
jupyter_core==5.7.2
|
||||
jupyterlab_pygments==0.3.0
|
||||
MarkupSafe==2.1.5
|
||||
matplotlib-inline==0.1.7
|
||||
mistune==3.0.2
|
||||
nbclient==0.10.0
|
||||
nbconvert==7.16.4
|
||||
nbformat==5.10.4
|
||||
packaging==24.1
|
||||
pandocfilters==1.5.1
|
||||
parso==0.8.4
|
||||
pexpect==4.9.0
|
||||
pickleshare==0.7.5
|
||||
pipreqs==0.5.0
|
||||
platformdirs==4.2.2
|
||||
prompt_toolkit==3.0.47
|
||||
ptyprocess==0.7.0
|
||||
pure-eval==0.2.2
|
||||
Pygments==2.18.0
|
||||
python-dateutil==2.9.0.post0
|
||||
python-dotenv==1.0.1
|
||||
pyzmq==26.0.3
|
||||
redis==5.0.6
|
||||
referencing==0.35.1
|
||||
requests==2.32.3
|
||||
rpds-py==0.18.1
|
||||
six==1.16.0
|
||||
soupsieve==2.5
|
||||
spotipy==2.24.0
|
||||
stack-data==0.6.3
|
||||
tinycss2==1.3.0
|
||||
tornado==6.4.1
|
||||
traitlets==5.14.3
|
||||
urllib3==2.2.2
|
||||
wcwidth==0.2.13
|
||||
webencodings==0.5.1
|
||||
Werkzeug==3.0.3
|
||||
WTForms==3.1.2
|
||||
yarg==0.1.9
|
||||
spotipy~=2.24.0
|
||||
python-dotenv~=1.0.1
|
||||
Flask~=3.0.3
|
||||
qrcode~=7.4.2
|
||||
requests~=2.32.3
|
||||
bootstrap-flask~=2.4.0
|
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Fête de la musique</title>
|
||||
<title>Make this playlist</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
{{ bootstrap.load_css() }}
|
||||
</head>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Fête de la musique</title>
|
||||
<title>Make this playlist</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
{{ bootstrap.load_css() }}
|
||||
</head>
|
||||
|
@ -41,7 +41,7 @@
|
||||
<h5 class="card-title"> {{ track[0] }} </h5>
|
||||
<p class="card-text">{{ track[1] }} - {{ track[2] }}</p>
|
||||
<audio controls><source src="{{ track[3] }}"></audio>
|
||||
<form method="GET" action="/add/{{roomid}}/{{playlistID}}/{{track[5]}}">
|
||||
<form method="GET" action="/add/room/{{roomid}}/playlist/{{playlistID}}/track/{{track[5]}}">
|
||||
<input type="hidden" name="track" value="{{ track[5] }}">
|
||||
<input type="submit" value="Add to the playlist" class="btn btn-primary">
|
||||
</form>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Fête de la musique</title>
|
||||
<title>Make this playlist</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
{{ bootstrap.load_css() }}
|
||||
</head>
|
||||
@ -30,7 +30,7 @@
|
||||
<input type="text" class="form-control" name="roomName" placeholder="Room name" />
|
||||
<small id="roomNameHelp" class="form-text text-muted">This will be the playlist name</small>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success">Create (require a Spotify account)</button>
|
||||
<button type="submit" class="btn btn-success">Create</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Fête de la musique</title>
|
||||
<title>Make this playlist - Search</title>
|
||||
{{ bootstrap.load_css() }}
|
||||
</head>
|
||||
<body>
|
||||
|
Loading…
x
Reference in New Issue
Block a user