Skip to content

Commit 2cbbb98

Browse files
author
Samuel Rodrigues
committed
Initial commit
0 parents  commit 2cbbb98

29 files changed

+1148
-0
lines changed

Diff for: .gitignore

+468
Large diffs are not rendered by default.

Diff for: .idea/.gitignore

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: .idea/inspectionProfiles/Project_Default.xml

+41
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: .idea/inspectionProfiles/profiles_settings.xml

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: .idea/mergify.iml

+33
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: .idea/misc.xml

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: .idea/modules.xml

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: .idea/vcs.xml

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: README.md

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Mergify
2+
Merge spotify playlists easily and automatically

Diff for: back/__init__.py

Whitespace-only changes.

Diff for: back/admin.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from django.contrib import admin
2+
3+
# Register your models here.

Diff for: back/apps.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.apps import AppConfig
2+
3+
4+
class BackConfig(AppConfig):
5+
default_auto_field = 'django.db.models.BigAutoField'
6+
name = 'back'

Diff for: back/manager/__init__.py

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from datetime import datetime
2+
from back.models import ParentPlaylist
3+
from typing import Any, List, Optional
4+
import spotipy
5+
6+
7+
class SpotifyAPIManager(spotipy.Spotify):
8+
def __init__(self, spotify_user):
9+
self.social_token = spotify_user.socialtoken_set.all()[0]
10+
super().__init__(self.social_token.token)
11+
12+
def check_token(self) -> bool:
13+
if self.social_token.expires_at.timestamp() < datetime.now().timestamp():
14+
return False
15+
return True
16+
17+
def get_playlists_not_in_parent(self, parent_id, user_uid) -> List[Any]:
18+
playlists = self.user_playlists(user_uid)
19+
parent = ParentPlaylist.objects.get(id=parent_id)
20+
for p in parent.playlist_set.all():
21+
for o in playlists["items"]:
22+
if o["id"] == p.get_id():
23+
playlists["items"].remove(o)
24+
25+
return playlists
26+
27+
def get_all_tracks(self, playlist_id: str) -> Optional[Any]:
28+
offset = 0
29+
tracks = []
30+
31+
while offset is not None:
32+
results = self.playlist_items(playlist_id, offset=offset, limit=100)
33+
tracks = tracks + results["items"]
34+
offset = offset + 100 if results["next"] is not None else None
35+
36+
return tracks
37+
38+
def delete_all_tracks(self, playlist_id: str, tracks: list[str]):
39+
to_delete = []
40+
41+
for i, item in enumerate(tracks):
42+
to_delete.append(item)
43+
if (i % 90 == 0 and i != 0) or i == len(tracks) - 1:
44+
self.playlist_remove_all_occurrences_of_items(playlist_id, to_delete)
45+
to_delete.clear()
46+
47+
def add_all_tracks(self, playlist_id: str, tracks: list[str]):
48+
to_add = []
49+
50+
for i, item in enumerate(tracks):
51+
to_add.append(item)
52+
if (i % 90 == 0 and i != 0) or i == len(tracks) - 1:
53+
self.playlist_add_items(playlist_id, to_add)
54+
to_add.clear()

Diff for: back/migrations/__init__.py

Whitespace-only changes.

Diff for: back/models.py

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from allauth.socialaccount.models import SocialAccount
2+
from django.contrib.auth.models import User
3+
from django.db import models
4+
5+
6+
# Create your models here.
7+
class BasePlaylist(models.Model):
8+
uri = models.CharField(max_length=96, null=True)
9+
name = models.CharField(max_length=512, default="")
10+
size = models.IntegerField(default=0)
11+
snapshot_id = models.CharField(max_length=128, null=True)
12+
13+
user = models.ForeignKey(User, on_delete=models.RESTRICT)
14+
spotify_user = models.ForeignKey(SocialAccount, on_delete=models.RESTRICT)
15+
16+
def get_id(self) -> str:
17+
if self.uri is None:
18+
return ""
19+
else:
20+
return get_id(str(self.uri))
21+
22+
def __str__(self):
23+
return f"{self.name}"
24+
25+
26+
class ParentPlaylist(BasePlaylist):
27+
allow_duplicates = models.BooleanField(default=False)
28+
29+
def get_local_items(self) -> List:
30+
items = []
31+
for playlist in self.playlist_set.all():
32+
items = items + playlist.item_set.all()
33+
34+
return items
35+
36+
def get_absolute_url(self):
37+
return reverse('edit', args=(self.id,))
38+
39+
def __str__(self):
40+
return f"{self.uri} | Owner: {self.user.username} | Allow Duplicates: {self.allow_duplicates}"
41+
42+
43+
class Playlist(BasePlaylist):
44+
parent = models.ForeignKey(ParentPlaylist, on_delete=models.CASCADE)
45+
46+
def __str__(self):
47+
return f"{self.name} | Parent: {self.parent.name}"
48+
49+
50+
class Item(models.Model):
51+
uri = models.CharField(max_length=96, null=True)
52+
playlist = models.ForeignKey(BasePlaylist, on_delete=models.CASCADE)
53+
54+
def get_id(self):
55+
return get_id(str(self.uri))
56+
57+
def __str__(self):
58+
return f"{self.uri} | Playlist: {self.playlist.name}"
59+
60+
def __hash__(self):
61+
return int(self.get_id() + self.playlist.get_id(), base=36)
62+
63+
def __eq__(self, other):
64+
if self.get_id() == other.get_id() and self.playlist.get_id() == other.playlist.get_id():
65+
return True
66+
else:
67+
return False

Diff for: back/tests.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from django.test import TestCase
2+
3+
# Create your tests here.

Diff for: back/urls.py

Whitespace-only changes.

Diff for: back/utils.py

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import regex
2+
from collections import Counter
3+
from typing import List, Tuple
4+
5+
6+
def get_id(uri: str) -> str:
7+
return regex.search(r"(?!spotify)?+(?!.com\/\w+\/)?(?!:\w+:)?[a-z|A-Z|\d]{20,}", uri)[0]
8+
9+
10+
def remove_duplicates(items: List) -> List:
11+
return list(dict.fromkeys(items))
12+
13+
14+
def difference(parent: List, child: List) -> Tuple[List[str], List[str]]:
15+
c_parent = Counter(parent)
16+
c_child = Counter(child)
17+
18+
c_total = {}
19+
20+
for c in c_child.keys():
21+
if c_parent.keys().__contains__(c) and c_child[c] != c_parent[c]:
22+
c_total[c] = c_child[c] - c_parent[c]
23+
else:
24+
c_total[c] = c_child[c]
25+
26+
for c in c_parent.keys():
27+
if not c_total.keys().__contains__(c):
28+
c_total[c] = -c_parent[c]
29+
30+
to_add = []
31+
to_remove = []
32+
if len(c_total) > 0:
33+
for d in c_total.keys():
34+
count = c_total[d]
35+
if count > 0:
36+
for i in range(0, count):
37+
to_add.append(d)
38+
elif count < 0:
39+
for i in range(0, abs(count)):
40+
to_remove.append(d)
41+
42+
return to_add, to_remove

Diff for: back/views.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from django.shortcuts import render
2+
3+
# Create your views here.

Diff for: manage.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env python
2+
"""Django's command-line utility for administrative tasks."""
3+
import os
4+
import sys
5+
6+
7+
def main():
8+
"""Run administrative tasks."""
9+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mergify.settings')
10+
try:
11+
from django.core.management import execute_from_command_line
12+
except ImportError as exc:
13+
raise ImportError(
14+
"Couldn't import Django. Are you sure it's installed and "
15+
"available on your PYTHONPATH environment variable? Did you "
16+
"forget to activate a virtual environment?"
17+
) from exc
18+
execute_from_command_line(sys.argv)
19+
20+
21+
if __name__ == '__main__':
22+
main()

Diff for: mergify/__init__.py

Whitespace-only changes.

Diff for: mergify/asgi.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""
2+
ASGI config for mergify project.
3+
4+
It exposes the ASGI callable as a module-level variable named ``application``.
5+
6+
For more information on this file, see
7+
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
8+
"""
9+
10+
import os
11+
12+
from django.core.asgi import get_asgi_application
13+
14+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mergify.settings')
15+
16+
application = get_asgi_application()

0 commit comments

Comments
 (0)