From a4b36c59f07f403aced8981238f92cfb39526d28 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 3 Nov 2019 01:56:30 -0600 Subject: [PATCH] properly add in spotify_explicit portion --- app/spotify_explicit | 1 - app/spotify_explicit/.gitignore | 111 +++++ app/spotify_explicit/LICENSE | 674 ++++++++++++++++++++++++++ app/spotify_explicit/README.md | 100 ++++ app/spotify_explicit/auth.py | 34 ++ app/spotify_explicit/graph.png | Bin 0 -> 34890 bytes app/spotify_explicit/main.py | 31 ++ app/spotify_explicit/process.py | 117 +++++ app/spotify_explicit/pull.py | 65 +++ app/spotify_explicit/recent.json | 1 + app/spotify_explicit/requirements.txt | 8 + 11 files changed, 1141 insertions(+), 1 deletion(-) delete mode 160000 app/spotify_explicit create mode 100644 app/spotify_explicit/.gitignore create mode 100644 app/spotify_explicit/LICENSE create mode 100644 app/spotify_explicit/README.md create mode 100644 app/spotify_explicit/auth.py create mode 100644 app/spotify_explicit/graph.png create mode 100644 app/spotify_explicit/main.py create mode 100644 app/spotify_explicit/process.py create mode 100644 app/spotify_explicit/pull.py create mode 100644 app/spotify_explicit/recent.json create mode 100644 app/spotify_explicit/requirements.txt diff --git a/app/spotify_explicit b/app/spotify_explicit deleted file mode 160000 index 9bd5b0c..0000000 --- a/app/spotify_explicit +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9bd5b0c7897ae1214d982c751c7e5e32e1678a21 diff --git a/app/spotify_explicit/.gitignore b/app/spotify_explicit/.gitignore new file mode 100644 index 0000000..b04e522 --- /dev/null +++ b/app/spotify_explicit/.gitignore @@ -0,0 +1,111 @@ +__pycache__/** +tracks/** +cache_xevioni/** +.cache-* +auth.json +export/** + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/app/spotify_explicit/LICENSE b/app/spotify_explicit/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/app/spotify_explicit/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/app/spotify_explicit/README.md b/app/spotify_explicit/README.md new file mode 100644 index 0000000..154ba8d --- /dev/null +++ b/app/spotify_explicit/README.md @@ -0,0 +1,100 @@ +# spotify-explicit + +## About + +The purpose of this repository/project is to capture my liked songs from my Spotify account, and then create a stacked bar graph representing my explicit vs "clean" songs. + +Yes, the project is super simple and doesn't have that much usefulness, but it's the first time I've genuinely used `matplotlib` or `logger` in a serious context and it was definitely interesting. + +## Demonstration + +![Output Matplotlib Stacked Bar-graph](./graph.png) + +--- + +```css +> python main.py +INFO:root:Pulling data from Spotify +INFO:root:Authorizing with Spotify via Spotipy +WARNING:root:May require User Interaction to authenticate properly! +INFO:root:Authorized with Spotify via Spotipy +WARNING:root:Clearing all files in tracks folder for new files +INFO:root:Cleared folder, ready to download new track files +INFO:root:Requesting 0 to 50 +INFO:root:Received 0 to 50 +INFO:root:Saved at "\tracks\saved-tracks-0-50.json" (150K) +INFO:root:Requesting 50 to 100 +INFO:root:Received 50 to 100 +INFO:root:Saved at "\tracks\saved-tracks-50-100.json" (151K) +INFO:root:Requesting 100 to 150 +INFO:root:Received 100 to 150 +INFO:root:Saved at "\tracks\saved-tracks-100-150.json" (146K) +INFO:root:Requesting 150 to 200 +INFO:root:Received 150 to 200 +INFO:root:Saved at "\tracks\saved-tracks-150-200.json" (147K) +INFO:root:Requesting 200 to 250 +INFO:root:Received 200 to 250 +INFO:root:Saved at "\tracks\saved-tracks-200-250.json" (146K) +INFO:root:Requesting 250 to 300 +INFO:root:Received 250 to 300 +INFO:root:Saved at "\tracks\saved-tracks-250-300.json" (137K) +INFO:root:Requesting 300 to 350 +INFO:root:Received 300 to 350 +INFO:root:Saved at "\tracks\saved-tracks-300-350.json" (140K) +INFO:root:Requesting 350 to 400 +INFO:root:Received 350 to 400 +INFO:root:Saved at "\tracks\saved-tracks-350-400.json" (149K) +INFO:root:Requesting 400 to 450 +INFO:root:Received 400 to 450 +INFO:root:Saved at "\tracks\saved-tracks-400-450.json" (140K) +INFO:root:Requesting 450 to 500 +INFO:root:Received 450 to 500 +INFO:root:Saved at "\tracks\saved-tracks-450-500.json" (149K) +INFO:root:Requesting 500 to 550 +INFO:root:Received 500 to 550 +INFO:root:Saved at "\tracks\saved-tracks-500-550.json" (137K) +INFO:root:Requesting 550 to 600 +INFO:root:Received 550 to 600 +INFO:root:Saved at "\tracks\saved-tracks-550-600.json" (137K) +INFO:root:Requesting 600 to 650 +INFO:root:Received 600 to 650 +INFO:root:Saved at "\tracks\saved-tracks-600-650.json" (135K) +INFO:root:Requesting 650 to 700 +INFO:root:Received 650 to 700 +INFO:root:Saved at "\tracks\saved-tracks-650-700.json" (140K) +INFO:root:Requesting 700 to 750 +INFO:root:Received 700 to 750 +INFO:root:Saved at "\tracks\saved-tracks-700-750.json" (134K) +INFO:root:Requesting 750 to 800 +INFO:root:Received 750 to 800 +INFO:root:Saved at "\tracks\saved-tracks-750-800.json" (137K) +INFO:root:Requesting 800 to 850 +INFO:root:Received 800 to 850 +INFO:root:Saved at "\tracks\saved-tracks-800-850.json" (132K) +INFO:root:Requesting 850 to 900 +INFO:root:Received 850 to 900 +INFO:root:Saved at "\tracks\saved-tracks-850-900.json" (143K) +INFO:root:Requesting 900 to 950 +INFO:root:Received 900 to 919 +INFO:root:Saved at "\tracks\saved-tracks-900-919.json" (55K) +INFO:root:Requested and saved 919 tracks split over 19 files (2 MB) +INFO:root:Reading track files +INFO:root:Read and parse 19 track files +INFO:root:Combining into single track file for ease of access +INFO:root:File combined with 919 items +INFO:root:Processing file... +INFO:root:Processed data, creating plot from data +INFO:root:Saving the figure to the 'export' folder +INFO:root:Showing plot to User +> +``` + +# Requirements + +The requirements for this project are outlined in the [requirements.txt](requirements.txt) file. + +You can use the `pip` tool to instantly download and install all required modules via `pip install -r requirements.txt`. + +## License + +This project uses the GNU General Public License, see the [LICENSE](./LICENSE) file for more information. \ No newline at end of file diff --git a/app/spotify_explicit/auth.py b/app/spotify_explicit/auth.py new file mode 100644 index 0000000..994ef6c --- /dev/null +++ b/app/spotify_explicit/auth.py @@ -0,0 +1,34 @@ +import logging, sys, os, json + +# Path to API Credentials file +PATH = os.path.join(sys.path[0], 'auth.json') + +# Ensure the file exists, if not, generate one and error with a reason +if not os.path.exists(PATH): + with open(PATH, 'w') as file: + # Dump a pretty-printed dictionary with default values + json.dump( + { + 'USERNAME' : 'Your Username Here', + 'CLIENT_ID' : 'Your Client ID Here', + 'CLIENT_SECRET' : 'Your Client Secret Here', + 'REDIRECT_URI' : 'Your Redirect URI Callback Here', + 'SCOPE' : ['Your Scopes Here'] + }, + file, + indent=3 + ) + # Error critically, then exit + logging.critical("No \'auth.json\' file detected, one has been created for you") + logging.critical("Please fill out with your Spotify credentials, and then restart the program") + sys.exit() + +# Open and parse file +FILE = json.load(open(PATH, 'r')) + +# Load all configuration variables +USERNAME = FILE['USERNAME'] +CLIENT_ID = FILE['CLIENT_ID'] +CLIENT_SECRET = FILE['CLIENT_SECRET'] +REDIRECT_URI = FILE['REDIRECT_URI'] +SCOPE = ' '.join(FILE['SCOPE']) \ No newline at end of file diff --git a/app/spotify_explicit/graph.png b/app/spotify_explicit/graph.png new file mode 100644 index 0000000000000000000000000000000000000000..20d6645d23a1851f86d2d503f3817b4096efddf8 GIT binary patch literal 34890 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 cache['expires_at']: + logging.info('Refreshing Spotify data by pulling tracks, this may take a moment.') + pull.main() + else: + logging.info('Spotify data deemed to be recent enough (under {} seconds old)'.format(cache['expires_in'])) + else: + pull.main() + +main() \ No newline at end of file diff --git a/app/spotify_explicit/process.py b/app/spotify_explicit/process.py new file mode 100644 index 0000000..2d7c2d5 --- /dev/null +++ b/app/spotify_explicit/process.py @@ -0,0 +1,117 @@ +import os +import sys +import json +import logging +import datetime +import collections +import numpy as np +import dateutil.parser +import PIL.Image as Image +import matplotlib.pyplot as plt + +# Gets all files in tracks folder, returns them in parsed JSON +def get_files(): + folder = os.path.join(sys.path[0], 'tracks') + files = [] + for file in os.listdir(folder): + with open(os.path.join(os.path.join(folder, file))) as file: + files.append( + json.load(file) + ) + return files + +# Simple function to combine a bunch of items from different files +def combine_files(files): + items = [] + for file in files: + items.extend(file['items']) + return items + +# Prints the data in a interesting format +def print_data(data): + for i, item in enumerate(data): + date = dateutil.parser.parse(item['added_at']) + explicit = '!' if item['track']['explicit'] else ' ' + track_name = item['track']['name'] + artists = ' & '.join(artist['name'] for artist in item['track']['artists']) + print('[{}] {} "{}" by {}'.format(date, explicit, track_name, artists)) + +def process_data(data): + # Process the data by Month/Year, then by Clean/Explicit + scores = {} + for item in data: + date = dateutil.parser.parse(item['added_at']).strftime('%b %Y') + if date not in scores.keys(): + scores[date] = [0, 0] + scores[date][1 if item['track']['explicit'] else 0] += 1 + + # Create simplified arrays for each piece of data + months = list(scores.keys())[::-1] + clean, explicit = [], [] + for item in list(scores.values())[::-1]: + clean.append(item[0]) + explicit.append(item[1]) + + # Done processing date properly, start plotting work + logging.info('Processed data, creating plot from data') + # Weird numpy stuff + n = len(scores.values()) + ind = np.arange(n) + width = 0.55 + # Resizer figuresize to be 2.0 wider + plt.figure(figsize=(10.0, 6.0)) + # Stacked Bars + p1 = plt.bar(ind, explicit, width) + p2 = plt.bar(ind, clean, width, bottom=explicit) # bottom= just has the bar sit on top of the explicit + # Plot labeling + plt.title('Song Count by Clean/Explicit') + plt.ylabel('Song Count') + plt.xlabel('Month') + plt.xticks(ind, months, rotation=270) # Rotation 90 will have the + plt.legend((p1[0], p2[0]), ('Explicit', 'Clean')) + fig = plt.gcf() # Magic to save to image and then show + + # Save the figure, overwriting anything in your way + logging.info('Saving the figure to the \'export\' folder') + export_folder = os.path.join(sys.path[0], 'export') + if not os.path.exists(export_folder): + os.makedirs(export_folder) + plt.tight_layout() + fig.savefig( + os.path.join( + export_folder, + 'export' + # datetime.datetime.now().strftime('%Y-%m-%d %H-%M-%S') + ), + dpi=100, + quality=95 + ) + + # Finally show the figure to + logging.info('Showing plot to User') + # plt.show() + + # Copy the figure to your clipboard to paste in Excel + # logging.info('Copying the plot data to clipboard') + # copy(months, clean, explicit) + +# Simple method for exporting data to a table like format +# Will paste into Excel very easily +def copy(months, clean, explicit): + from pyperclip import copy + top = 'Period\tClean\tExplicit\n' + copy(top + '\n'.join([ + f'{item[0]}\t{item[1]}\t{item[2]}' for item in zip(months, clean, explicit) + ])) + +def main(): + # logging.basicConfig(level=logging.INFO) + logging.info("Reading track files") + files = get_files() + logging.info(f"Read and parse {len(files)} track files") + logging.info("Combining into single track file for ease of access") + data = combine_files(files) + data.sort(key=lambda item : dateutil.parser.parse(item['added_at']).timestamp(), reverse=True) + logging.info(f'File combined with {len(data)} items') + logging.info('Processing file...') + process_data(data) \ No newline at end of file diff --git a/app/spotify_explicit/pull.py b/app/spotify_explicit/pull.py new file mode 100644 index 0000000..da4e202 --- /dev/null +++ b/app/spotify_explicit/pull.py @@ -0,0 +1,65 @@ +import os +import sys +import auth +import json +import shutil +import pprint +import spotipy +import logging +from hurry.filesize import size, alternative +import spotipy.util as util + +def main(): + # Get Authorization + logging.basicConfig(level=logging.INFO) + logging.info('Authorizing with Spotify via Spotipy') + logging.warning('May require User Interaction to authenticate properly!') + token = util.prompt_for_user_token( + username=auth.USERNAME, + scope=auth.SCOPE, + client_id=auth.CLIENT_ID, + client_secret=auth.CLIENT_SECRET, + redirect_uri=auth.REDIRECT_URI + ) + sp = spotipy.Spotify(auth=token) + logging.info('Authorized with Spotify via Spotipy') + + tracks_folder = os.path.join(sys.path[0], 'tracks') + logging.warning('Clearing all files in tracks folder for new files') + if os.path.exists(tracks_folder): + shutil.rmtree(tracks_folder) # Delete folder and all contents (old track files) + os.makedirs(tracks_folder) # Recreate the folder just deleted + logging.info('Cleared folder, ready to download new track files') + + curoffset, curlimit = 0, 50 + while curoffset >= 0: + # Request and identify what was received + logging.info('Requesting {} to {}'.format(curoffset, curoffset + curlimit)) + response = sp.current_user_saved_tracks(limit=curlimit, offset=curoffset) + received = len(response['items']) + logging.info('Received {} to {}'.format(curoffset, curoffset + received)) + # Create path/filename + filename = f'saved-tracks-{curoffset}-{curoffset + received}.json' + filepath = os.path.join(tracks_folder, filename) + # Save track file + with open(filepath, 'w+') as file: + json.dump(response, file) + logging.info('Saved at "{}" ({})'.format( + f'\\tracks\\{filename}', + size(os.path.getsize(filepath))) + ) + # Decide whether we have received all possible tracks + if received < curlimit: + logging.info('Requested and saved {} tracks split over {} files ({})'.format( + curoffset + received, + len(os.listdir(tracks_folder)), + size( + sum( + os.path.getsize(os.path.join(tracks_folder, file)) for file in os.listdir(tracks_folder) + ), + system=alternative + ) + )) + break + # Continuing, so increment offset + curoffset += curlimit \ No newline at end of file diff --git a/app/spotify_explicit/recent.json b/app/spotify_explicit/recent.json new file mode 100644 index 0000000..bf7a4cf --- /dev/null +++ b/app/spotify_explicit/recent.json @@ -0,0 +1 @@ +{"last_generated": 1572327849} \ No newline at end of file diff --git a/app/spotify_explicit/requirements.txt b/app/spotify_explicit/requirements.txt new file mode 100644 index 0000000..09af153 --- /dev/null +++ b/app/spotify_explicit/requirements.txt @@ -0,0 +1,8 @@ +hurry.filesize +pyperclip +numpy +spotipy +matplotlib +Pillow +python_dateutil +hurry \ No newline at end of file