19 |
66 |
235 |
236 |
237 |
238 |
239 |
--------------------------------------------------------------------------------
/addons/Запуск с телефона/readme.md:
--------------------------------------------------------------------------------
1 | ## Описание
2 |
3 | Аддон позволяет запускать функции вручную через html-страничку. В том числе со смартфона.
4 | Возможна передача аргументов. Например, `id` плейлиста, чтобы очистить его от недавней истории прослушиваний.
5 |
6 | [Инструкция по установке](https://github.com/Chimildic/goofy/discussions/9)
--------------------------------------------------------------------------------
/addons/История прослушиваний от приложений/history-manager.js:
--------------------------------------------------------------------------------
1 | const HistoryManager = (function () {
2 | const FILES = ['SpotifyHistoryPC', 'SpotifyHistoryPhone'];
3 | return {
4 | getTracks: getTracks,
5 | removeTracks: removeTracks,
6 | readTrackIds: readTrackIds,
7 | };
8 |
9 | function getTracks() {
10 | return SpotifyRequest.getFullObjByIds('tracks', readTrackIds(), 50);
11 | }
12 |
13 | function removeTracks(tracks) {
14 | let trackHistoryIds = readTrackIds();
15 | return tracks.filter((track) => !trackHistoryIds.includes(track.id));
16 | }
17 |
18 | function readTrackIds() {
19 | let tracks = [];
20 | for (let i = 0; i < FILES.length; i++) {
21 | let data = tryParseJSON(getFile(FILES[i]));
22 | if (data.play_history && data.play_history.tracks) {
23 | let validTracks = data.play_history.tracks.filter(t => t.uri.includes('spotify:track'));
24 | Combiner.push(tracks, validTracks);
25 | }
26 | }
27 | let trackIds = tracks.map((track) => track.uri.replace('spotify:track:', ''));
28 | return Array.from(new Set(trackIds));
29 | }
30 |
31 | function tryParseJSON(file) {
32 | if (!file) return {};
33 | try {
34 | let dataAsString = tryGetBlob(file);
35 | let hashIndex = dataAsString.indexOf('#');
36 | return JSON.parse(dataAsString.substring(hashIndex + 1));
37 | } catch (error) {
38 | Admin.printError(error.stack);
39 | return {};
40 | }
41 | }
42 |
43 | function tryGetBlob(file) {
44 | if (!file) return '';
45 | try {
46 | return file.getBlob().getDataAsString();
47 | } catch (error) {
48 | Admin.printError('При получении данных из файла произошла ошибка\n', error.stack);
49 | Admin.pause(5);
50 | return tryGetBlob(file);
51 | }
52 | }
53 |
54 | function getFile(filename) {
55 | let files = Cache.UserFolder.getFilesByName(filename);
56 | if (files.hasNext()) {
57 | return files.next();
58 | }
59 | }
60 | })();
--------------------------------------------------------------------------------
/addons/История прослушиваний от приложений/readme.md:
--------------------------------------------------------------------------------
1 | ## Описание
2 |
3 | Аддон позволяет прочитать файлы истории прослушиваний с десктоп и мобильного приложения Spotify.
4 | Способен удалить ее из заданного массива `removeTracks`. Также перевести в полноценный массив треков `getTracks`.
5 |
6 | > У такой истории нет даты прослушивания
7 |
8 | Пример использования - удалить прослушанные ранее любимые треки
9 | ```js
10 | let tracks = Source.getSavedTracks();
11 | HistoryManager.removeTracks(tracks);
12 | ```
13 |
14 | ## Установка
15 |
16 | > Более подробно [в теме](https://github.com/Chimildic/goofy/discussions/53)
17 |
18 | 1. Создайте новый скрипт и скопируйте в него содержимое `history-manager.js`.
19 | 2. Добавьте в папку `Goofy Data` на Google Диске файлы истории прослушиваний от приложений Spotify
20 |
21 | Файл называется `context_player_state_restore`
22 |
23 | Путь Windows: C:\Users\ **Имя пользователя** \AppData\Local\Spotify\Users\ **ИД пользователя**
24 |
25 | Путь Android: /android/data/com.spotify.music/files/spotifycache/users/ **ИД пользователя**
26 |
27 | 3. Переименуйте файлы, если используете оба.
28 | 3. Добавьте имена этих файлов в константу `FILES`.
29 |
30 | > Существуют способы автоматической загрузки файлов в Google Диск с андроид и ПК. Например, программа `Air Explorer` для ПК.
31 | >
32 | > Это выходит за рамки библиотеки. Если вы знаете способ, можете описать его [на форуме](https://github.com/Chimildic/goofy/discussions).
33 | >
34 | > Такой подход автоматизирует доставку истории прослушиваний и делает ее более точной в сравнении с `RecentTracks`.
35 |
--------------------------------------------------------------------------------
/addons/Новые релизы по жанрам/release.js:
--------------------------------------------------------------------------------
1 | // Описание https://github.com/Chimildic/goofy/discussions/36
2 | const Release = (function () {
3 | const URL_BASE = 'http://everynoise.com/new_releases_by_genre.cgi';
4 | return {
5 | getTracks: getTracks,
6 | };
7 |
8 | function getTracks(params) {
9 | return getAlbums(params).reduce((tracks, album) => {
10 | return Combiner.push(tracks, album.tracks.items);
11 | }, []);
12 | }
13 |
14 | function getAlbums(params) {
15 | params.date = params.date ? params.date.replace(/-/g, '') : '';
16 | params.region = params.region || 'RU';
17 | params.type = params.type || 'album,single';
18 |
19 | let template = `%s?genre=%s®ion=%s&date=%s&hidedupes=on`;
20 | let url = Utilities.formatString(template, URL_BASE, params.genre, params.region, params.date);
21 |
22 | let albumIds = parseAlbumIds(url, params.genre);
23 | let albums = SpotifyRequest.getFullObjByIds('albums', albumIds, 20);
24 | return albums.filter((album) => params.type.includes(album.album_type));
25 | }
26 |
27 | function parseAlbumIds(url, genre) {
28 | let albumsIds = [];
29 | let cheerio = createCherio(url);
30 | let rootId = genre.replace(/[- ]/g, '');
31 | let root = cheerio(`#${rootId}`).next();
32 | cheerio('.albumbox', '', root).each((index, node) => {
33 | let uri = cheerio('a[href^="spotify:album:"]', '', node).attr('href');
34 | let id = uri.split(':')[2];
35 | albumsIds.push(id);
36 | });
37 | return albumsIds;
38 | }
39 |
40 | function createCherio(url) {
41 | let content = CustomUrlFetchApp.fetch(url).getContentText();
42 | return Cheerio.load(content);
43 | }
44 | })();
45 |
--------------------------------------------------------------------------------
/addons/Треки с FM-радио/radio.js:
--------------------------------------------------------------------------------
1 | // Описание https://github.com/Chimildic/goofy/discussions/35
2 | const Radio = (function () {
3 | const URL_BASE = 'http://the-radio.ru/';
4 | return {
5 | getTracks: getTracks,
6 | getTopTracks: getTopTracks,
7 | };
8 |
9 | function getTracks(station, limit) {
10 | return start(station, 'tracklist', limit);
11 | }
12 |
13 | function getTopTracks(station, limit) {
14 | return start(station, 'top', limit);
15 | }
16 |
17 | function start(station, type, limit) {
18 | let strTracks = Selector.sliceFirst(parseTracks(station, type), limit);
19 | return Search.multisearchTracks(strTracks);
20 | }
21 |
22 | function parseTracks(station, type) {
23 | let tracknames = [];
24 | let cheerio = createCherio(`${URL_BASE}playlist/${station}`);
25 | let root = type == 'top' ? cheerio('#tophit-list') : cheerio('#track-list');
26 | cheerio('.tkl-title', '', root).each((i, titleNode) => {
27 | let [artist, track] = loadRow(titleNode.children);
28 | tracknames.push(`${artist} ${track ? parseNameTrack(track) : ''}`.formatName());
29 | });
30 | return tracknames.slice(1);
31 |
32 | function loadRow(children) {
33 | let row = [];
34 | cheerio(children).each((j, childNode) => {
35 | let col = cheerio(childNode).text().trim();
36 | col.length > 0 && row.push(col);
37 | })
38 | return row;
39 | }
40 | }
41 |
42 | function createCherio(url) {
43 | return Cheerio.load(CustomUrlFetchApp.fetch(url).getContentText());
44 | }
45 |
46 | function parseNameTrack(item) {
47 | let re = /( x | feat*\w+| vs*\W+|,|\/|[(]| \+ id)/gi;
48 | return item.split(re)[0];
49 | }
50 | })();
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | function setProperties() {
2 | // Описание параметров: chimildic.github.io/goofy/#/config
3 | UserProperties.setProperty('CLIENT_ID', 'вашеЗначение');
4 | UserProperties.setProperty('CLIENT_SECRET', 'вашеЗначение');
5 | UserProperties.setProperty('PRIVATE_CLIENT_ID', 'вашеЗначение');
6 | UserProperties.setProperty('PRIVATE_CLIENT_SECRET', 'вашеЗначение');
7 |
8 | UserProperties.setProperty('LASTFM_API_KEY', 'вашеЗначение');
9 | UserProperties.setProperty('MUSIXMATCH_API_KEY', 'вашеЗначение');
10 |
11 | UserProperties.setProperty('ON_SPOTIFY_RECENT_TRACKS', 'true');
12 | UserProperties.setProperty('ON_LASTFM_RECENT_TRACKS', 'false');
13 | UserProperties.setProperty('COUNT_RECENT_TRACKS', '60000');
14 |
15 | UserProperties.setProperty('LASTFM_LOGIN', 'вашЛогин');
16 | UserProperties.setProperty('LASTFM_RANGE_RECENT_TRACKS', '30');
17 |
18 | UserProperties.setProperty('LOG_LEVEL', 'info');
19 | UserProperties.setProperty('LOCALE', 'RU');
20 | UserProperties.setProperty('REQUESTS_IN_ROW', '20');
21 | UserProperties.setProperty('MIN_DICE_RATING', '0.6005');
22 | }
23 |
24 | // Чтобы посмотреть текущие значения параметров
25 | // function logProperties() {
26 | // console.log(UserProperties.getProperties())
27 | // }
28 |
29 | // Чтобы сбросить авторизацию и параметры
30 | // function reset() {
31 | // Admin.reset()
32 | // setProperties()
33 | // }
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/_navbar.md:
--------------------------------------------------------------------------------
1 | * English
2 | * [Install Google Translator (clickable)](https://chrome.google.com/webstore/detail/aapbdbdomjkkjkaonfhkkikfgjllcleb)
3 | * Отзывы
4 | * [Прочитать](https://docs.google.com/forms/d/e/1FAIpQLScjlT9SbAJ8h3xkUYkw13g81URgPXLbOiEdTJaPWkIjvpkFGg/viewanalytics)
5 | * [Добавить](https://docs.google.com/forms/d/e/1FAIpQLScjlT9SbAJ8h3xkUYkw13g81URgPXLbOiEdTJaPWkIjvpkFGg/viewform)
6 | * [Форум](https://github.com/Chimildic/goofy/discussions)
7 | * [Список изменений](/changelog.md)
8 |
--------------------------------------------------------------------------------
/docs/_sidebar.md:
--------------------------------------------------------------------------------
1 | - **Начало работы**
2 | - [Обзор](/)
3 | - [Установка](/install.md)
4 | - [Первый плейлист](/first-playlist.md)
5 | - [Audiolist](/audiolist.md)
6 | - **Использование**
7 | - [Принцип работы](/details.md)
8 | - [Шаблоны](/template.md)
9 | - [Аддоны](/addon.md)
10 | - [Полезный опыт](/best-practices.md)
11 | - **Руководство**
12 | - [Список модулей](/reference/index.md)
13 | - [Решение ошибок](/errors.md)
14 | - [Параметры](/config.md)
15 | - [Миграция](/migrate2.md)
16 | - [Расширенная настройка](/tuning.md)
17 | - **Поддержать**
18 | -
19 |
--------------------------------------------------------------------------------
/docs/addon.md:
--------------------------------------------------------------------------------
1 | # Аддоны
2 |
3 | Полезные расширения основных функций
4 |
5 | ### Управление с телефона
6 |
7 | Позволяет запускать функции вручную, без триггеров и редактора. В том числе с передачей аргументов.
8 |
9 | Все подробности [в теме](https://github.com/Chimildic/goofy/discussions/9).
10 |
11 | 
12 |
13 | ### Импорт треков с радио
14 |
15 | Позволяет импортировать треки с FM и интернет-радио.
16 |
17 | Несколько вариантов. Выбор зависит от того какая станция вам нужна:
18 | - [the-radio](https://github.com/Chimildic/goofy/discussions/35)
19 | - [pcradio](https://github.com/Chimildic/goofy/discussions/60)
20 | - [AudioAddict](https://github.com/Chimildic/goofy/discussions/57) (di.fm и прочее)
21 | - [Radio Paradise](https://4pda.to/forum/index.php?s=&showtopic=715234&view=findpost&p=105313450)
22 |
23 | ?> Если не нашли желаемую станцию или хотите другой источник, напишите [на форум](https://github.com/Chimildic/goofy/discussions)
24 |
25 | ### Рекомендации из прошлого
26 |
27 | Для пользователей Last.fm. [Тема](https://github.com/Chimildic/goofy/discussions/91) описывает пример использования функции [getCustomTop](/reference/lastfm?id=getcustomtop), с помощью которой создается плейлист `Омут памяти`, состоящий из хороших, но забытых треков.
28 |
29 | ### Скипы прослушиваний
30 |
31 | [Данный способ](https://github.com/Chimildic/goofy/discussions/53) основан на чтении файлов, которые Spotify создает локально на устройстве. Тем самым давая доступ к трекам, которые быстро пропущены и не попали в зону видимости [основного механизма сбора истории](/details?id=История-прослушиваний).
32 |
33 | ### Новые релизы
34 |
35 | Импортирует треки выбранного жанра с сайта [Every Noise](https://everynoise.com/new_releases_by_genre.cgi). Описание [в теме](https://github.com/Chimildic/goofy/discussions/36).
36 |
37 | ?> Для получения релизов отслеживаемых исполнителей, воспользуйтесь функцией [getArtistsTracks](/reference/source?id=getartiststracks) с параметром `release_date`
38 |
39 | ### Прочее
40 |
41 | Ещё больше примеров описано [на форуме](https://github.com/Chimildic/goofy/discussions) под категорией _личный опыт_.
42 |
--------------------------------------------------------------------------------
/docs/audiolist.md:
--------------------------------------------------------------------------------
1 | # Audiolist
2 |
3 | Начиная с версии `2.1` goofy умеет принимать запросы от [андроид приложения Audiolist](https://play.google.com/store/apps/details?id=ru.chimildic.audiolist) и возвращать ответ при необходимости.
4 |
5 | ## Настройка
6 |
7 | ### goofy
8 |
9 | 1. Обновите goofy до последней версии ([файл library](https://github.com/Chimildic/goofy/blob/main/library.js)). Для перехода с версии `1.x` на `2.x` воспользуйтесь [миграцией](/migrate2).
10 | 2. В редакторе кода нажмите _начать развертывание_ > _новое развертывание_. Убедитесь, что выбрана конфигурация с веб-приложением и доступом для всех. Нажмите _начать развертывание_.
11 |
12 | 
13 |
14 | 3. Скопируйте `идентификатор развертывания` и удобным вам способом перенесите на устройство где установлен Audiolist.
15 |
16 | !> Не передавайте идентификатор другим пользователям. Они смогут запускать ваши функции.
17 |
18 | ### Audiolist
19 |
20 | 1. Создайте программу в Audiolist и добавьте из общего списка команду `функция goofy`.
21 | 2. При заполнении формы команды оставьте поля с переменными пустыми. Укажите `идентификатор развертывания`, полученный при настройке goofy. В качестве _имени функции_ для примера используем `Audiolist.hello`
22 |
23 | 
24 |
25 | 3. Выйдите из редактора программы и запустите выполнение программы. Если все сделано верно, в логах появится сообщение от goofy.
26 |
27 | ## Использование
28 |
29 | Важно три вещи:
30 |
31 | 1. Правильное имя функции в настройках команды. Регистр букв имеет значение.
32 | 2. Возврат результата. Audiolist использует типизированный язык программирования и ожидает строгий формат ответа.
33 | 3. Обновление развертывания при любом изменении кода goofy. Развертывание - это изолированная копия. Если добавить/изменить функцию и не обновить развертывание, Audiolist не сможет её вызвать.
34 |
35 | ### Пустой ответ
36 |
37 | ```js
38 | function doSomething() {
39 | // Алгоритм вашей функции
40 | // ...
41 |
42 | // Возврат результата
43 | return Audiolist.response()
44 | }
45 | ```
46 |
47 | ### Возврат текста
48 |
49 | Помимо текста можно указать тип: обычное, предупреждение, ошибка. От типа зависит цвет сообщения в логах. Прерывания программы не будет.
50 |
51 | ```js
52 | function doSomethingMessage() {
53 | // Алгоритм вашей функции
54 | // ...
55 |
56 | let text = "Этот текст появится в логах Audiolist"
57 | return Audiolist.responseMessage(text, Audiolist.MESSAGE_TYPES.WARNINNG)
58 | }
59 | ```
60 |
61 | ### Возврат треков
62 |
63 | Чтобы Audiolist смог преобразовать массив элементов от goofy в понятный для себя формат, необходимо указать один из типов `Audiolist.VARIABLE_TYPES`. Пример кода ниже возвращает 20 любимых Spotify треков. Однако на практике в такой функции нет смысла. Audiolist имеет свои команды для сбора треков. Проще получить те же любимые треки не прибегая к goofy. Но будет полезно для импорта других источников (кэш, радио и прочее).
64 |
65 | ```js
66 | function doSomethingItems() {
67 | let tracks = Source.getSavedTracks(20)
68 | // ...
69 |
70 | return Audiolist.responseItems(tracks, Audiolist.VARIABLE_TYPES.SPOTIFY_TRACK)
71 | }
72 | ```
73 |
74 | ### Получение треков
75 |
76 | Если указать источник данных для команды "Функция goofy", Audiolist отправит его в указанную функцию. При этом одновременно можно ответить текстом и треками.
77 |
78 | ```js
79 | function complexExample(data) {
80 | let tracksFromAudiolist = data.items
81 | // ...
82 |
83 | let tracks = // ...
84 |
85 | return Audiolist.response({
86 | message: 'Сообщение для логов',
87 | messageType: Audiolist.MESSAGE_TYPES.DEFAULT,
88 | variableType: Audiolist.VARIABLE_TYPES.SPOTIFY_TRACK,
89 | items: tracks,
90 | })
91 | }
92 | ```
93 |
94 | ### Получение параметров
95 |
96 | На стороне Audiolist есть поле `параметры` в `ini` формате. На стороне goofy данные парсятся в объект по пути `data.ini`. Необработанные данные по пути `data.iniRaw`.
97 |
98 | В `ini` формате на каждой строке находятся пары вида `ключ=значение`. Имя ключа произвольно, но без пробелов и на английском. Регистр букв важен.
99 |
100 | Например, задача получить от goofy 50 треков из файла с кэшем. Чтобы функция получилась универсальной, имя файла и количество треков сделаем параметрами.
101 |
102 | ```
103 | filename=SavedTracks
104 | size=50
105 | ```
106 |
107 | ```js
108 | function getTracksFromCache(data) {
109 | let tracks = Cache.read(data.ini.filename)
110 | return Audiolist.response({
111 | message: `${data.ini.size} треков из файла "${data.ini.filename}"`,
112 | messageType: Audiolist.MESSAGE_TYPES.DEFAULT,
113 | variableType: Audiolist.VARIABLE_TYPES.SPOTIFY_TRACK,
114 | items: Selector.sliceFirst(tracks, data.ini.size),
115 | })
116 | }
117 | ```
118 |
119 | В более сложных сценариях может понадобиться массив или вложенные объекты. Значения массива разделяются запятой. Вложенные объекты создаются точкой.
120 |
121 | ```
122 | array=a,b,c
123 | playlist.other.test=false
124 |
125 | [playlist]
126 | name=Имя плейлиста
127 | description=Описание
128 |
129 | [playlist.counters]
130 | size=10
131 | likes=3
132 | ```
133 |
134 | В результате goofy создаст объект со следующей структурой.
135 | ```
136 | {
137 | array: ["a", "b", "c"],
138 | playlist: {
139 | name: "Имя плейлиста",
140 | description: "Описание",
141 | other: {
142 | test: false
143 | }
144 | counters: {
145 | size: 10,
146 | likes: 3
147 | }
148 | }
149 | }
150 | ```
151 |
152 | ## Обновление развертывания
153 |
154 | Нажмите _начать развертывание_ > _управление развертываниями_. В левом списке выберите Audiolist, нажмите кнопку редактирования. Выберите _новую версию_ в списке и нажмите _начать развертывание_.
155 |
156 | Иногда Apps Script сбрасывает названия развертываний (похоже на баг). Вам нужно обновлять то развертывание, идентификатор которого указан в команде "Функция goofy".
157 |
158 | 
159 |
160 | ### Архив развертываний
161 |
162 | Старые версии развертываний попадают в архив. Apps Script устанавливает лимит на количество развертываний (~200). Если вам укажут на лимит или просто накопилось много ненужных версий, в левом меню Apps Script выберите _история версий_. Справа появится список _история проекта_. Внизу списка есть кнопка _массового удаления версий_. Актуальную версию не удалит.
163 |
--------------------------------------------------------------------------------
/docs/changelog.md:
--------------------------------------------------------------------------------
1 | # Список изменений
2 |
3 | Текущая версия библиотеки отражена в константе `VERSION` файла `library`
4 |
5 | Для добавления своих функций или переопределения существующих, используйте [инструкцию](https://github.com/Chimildic/goofy/discussions/18).
6 |
7 | [Скопируйте обновленный код](https://script.google.com/d/1DnC4H7yjqPV2unMZ_nmB-1bDSJT9wQUJ7Wq-ijF4Nc7Fl3qnbT0FkPSr/edit?usp=sharing).
8 |
9 | ### Версия 2.0.4
10 | - Выполните [миграцию](/migrate2.md)
11 | - Добавлена функция [getRecomArtists](/reference/source?id=getrecomartists) на замену ошибкам от [getRelatedArtists](/reference/source?id=getrelatedartists) после обновленной политике Spotify
12 |
13 | ## Версия 1.8.5
14 | - Удален модуль `EveryNoise`. Сайт перестал давать релизы сразу в HTML-странице. Теперь страница наполняется динамически, что в рамках Apps Script невозможно парсить.
15 | - Удалена функция `Source.getReleasesByArtists`.
16 | - Добавлена функция [Source.getRecentReleasesByArtists](/reference/source?id=getrecentreleasesbyartists). Алгоритм получения недавних релизов полностью переделан.
17 | - Добавлен [шаблон для получения недавних релизов](/template?id=Новые-релизы-по-частям) при очень большом количестве исполнителей.
18 |
19 | ## Версяи 1.8.4
20 | - Функции [Order.shuffle](/reference/order?id=shuffle) добавлен аргумент `factor`.
21 | - Добавлено прерывание в случае сохранения треков в несуществующий плейлист (когда id строго зашит в аргументы функции `Playlist.save*`).
22 | - Добавлено описание действий при ошибке `Access not granted or expired`.
23 | - Значение параметра `REQUESTS_IN_ROW` принудительно снижено до 20, если текущее 40. Spotify стал часто давать теневой бан на сутки и больше когда это значение большое.
24 | - Функции `SpotifyRequest.get*` отфильтровывают нулевые элементы, чтобы избежать прерывание скриптов из-за неожиданных ответов Spotify.
25 | - Фикс для функций модуля Library. Spotify изменил формат запроса.
26 | - Фикс резервной копии треков плейлиста в случае неожиданной ошибки. Имя файла не будет содержать даты. Версии файла хранит Google Диск.
27 |
28 | ## Версия 1.8.3
29 | - [12.09.22] Правка редко встречающихся багов
30 |
31 | ## Версия 1.8.2
32 |
33 | - [#175](https://github.com/Chimildic/goofy/discussions/175) Добавлена обработка ошибки при отправке треков в плейлист. Изменен ключ, указывающий место добавления треков: вместо булево `toEnd`теперь строка `position`. По умолчанию треки добавляются в конец плейлиста.
34 | - [#187](https://github.com/Chimildic/goofy/discussions/187) Повторное чтение файла при неизвестной ошибке Google Диска
35 | - При поиске наилучшего совпадения применяется трасляция кириллицы в латиницу.
36 | - Фикс ошибки, при которой треки от функции `getSavedAlbumTracks` не записывались в кэш.
37 | - Фикс ошибки, при которой импорт прерывался из-за нулевого результата поиска от Spotify.
38 | - Фикс: первый запуск Clerk-задачи происходил в любое время, игнорируя целевое значение.
39 |
40 | ## Версия 1.8.0
41 |
42 | - Изменен алгоритм поиска налучшего совпадения при импорте в Spotify. Позволит уменьшить количество "левых" треков, когда искомого нет в базе Spotify. На точность сравнения можно повлиять изменяя параметр `MIN_DICE_RATING` в файле _config_.
43 | - Увеличены лимиты в разных местах.
44 | - Страна для `removeUnavailable` по умолчанию берется из данных об аккаунте.
45 | - Удалена функция `sendMusicRequest`.
46 |
47 | ## Версия 1.7.0
48 |
49 | - Добавьте библиотеку [Cheerio](https://github.com/Chimildic/goofy/discussions/91#discussioncomment-1931923).
50 | - [#153](https://github.com/Chimildic/goofy/issues/153) Добавлено кэширование лайков. Настройте [часовой пояс](/tuning?id=Часовой-пояс).
51 | - [#159](https://github.com/Chimildic/goofy/issues/159) Добавлены функции парсинга элементов со страниц тега last.fm: [getTracksByTag](/reference?id=gettracksbytag), [getArtistsByTag](/reference?id=getartistsbytag), [getAlbumsByTag](/reference?id=getalbumsbytag).
52 | - (10.01.22) [#155](https://github.com/Chimildic/goofy/issues/155#issuecomment-1008871981) Изменен алгоритм подбора релизов из-за возникшего недостатка у имен исполнителей из общепринятых слов. Обязательно наличие библиотеки Cheerio.
53 |
54 | ## Версия 1.6.3
55 | - Модуль Yandex полностью удален. Яндекс перестал отвечать на запросы. Многие функции может заменить расширение [YaMuTools](https://github.com/Chimildic/YaMuTools). Но у него нет работы по расписанию.
56 | - [#155](https://github.com/Chimildic/goofy/issues/155) Новая функция [Source.getReleasesByArtists](/reference/source?id=getreleasesbyartists). Собирает релизы исполнителей значительно быстрее прямого перебора, использовавшегося ранее при комбинации разных функций.
57 | - [#162](https://github.com/Chimildic/goofy/issues/162) Аддон определения и фильтрации основного языка трека перенесен в основной код [Filter.detectLanguage](/reference/filter?id=detectlanguage). При первом запуске из редактора потребует разрешить доступ к Google Таблицам.
58 | - У большей части документации по функциям изменен стиль написания, а также модули разделены по отдельным файлам (старые ссылки на `/func` перестают работать).
59 | - В навигацию по сайту добавлена ссылка на отзывы.
60 |
61 | ## Версия 1.6.2
62 | - Уменьшено количество обращений к Google Диску. Операции чтения/записи одного и того же файла в рамках одного выполнения скрипта пройдут за меньшее время.
63 | - Удалена функция `Cache.clear`. Используйте `Cache.write` для перезаписи файла.
64 |
65 | ## Версия 1.6.1
66 | - Изменена структура данных на Диске для поддержки нескольких аккаунтов Spotify на одном аккаунте Google.
67 | При первом запуске все файлы из папки `Goofy Data` будут помещены в новую папку с идентификаторов пользователя, для которого выполняется скрипт. Дальнейшие запуски будут изменять файлы из соответствующей пользователю папки.
68 |
69 | Если у вас один аккаунт Spotify, ничего делать не нужно.
70 |
71 | Если ранее уже настраивали несколько аккаунтов Spotify на одном аккаунте Google, выполните следующие действия:
72 | - запустите любой скрипт от первого аккаунта
73 | - верните файлам этого аккаунта стандартные имена на Диске
74 | - файлы других аккаунтов перенесите в папку `Goofy Data`
75 | - повторите для каждого аккаунта
76 | - Добавлена возможность [создавать папки](/best-practices?id=Путь-до-файла)
77 |
78 | ## Версия 1.6.0
79 | - Исправлены ошибки в отправке запросов. Количество пауз при запросах увеличится, время паузы сократится (при статусе ответа 429).
80 | - Добавление треков [RecentTracks.appendTracks](/reference/recenttracks?id=appendtracks) больше не сбивает алгоритм поиска новых прослушиваний в триггере обновления истории.
81 | - [Cache.append](/reference/cache?id=append) больше не добавляет данные в исходный массив (используйте [push](/reference/push?id=push)). Добавляет только в кэш файл. Возвращает количество всех элементов после добавления.
82 | - Добавлена возможность отключить консольные сообщения от функций библиотеки. Глобальная настройка задается в [config-файле](/config). Временная смена через функцию `Admin.setLogLevelOnce`. Из функций удалены аргументы `isLogging`.
83 | - Без указания имени пользователя все функции из модуля `Lastfm` подставляют значение из `config-файла`.
84 |
85 | ## Версия 1.5.4
86 | - Добавлена попытка повторного чтения/записи при неизвестной ошибки службы Google Диска.
87 | - Добавлены функции: [Lastfm.rangeTags](/reference/lastfm?id=rangetags) и ряд Lastfm.getTop*ByTag.
88 | - [addToQueue](/reference/player?id=addtoqueue) может добавлять массив треков в очередь.
89 | - К [mineTracks](/reference/source?id=minetracks) добавлен параметр для пропуска элементов.
90 | - К [removeUnavailable](/reference/filter?id=removeunavailable) добавлен аргумент отключения сообщений с логами.
91 |
92 | ## Версия 1.5.3
93 | - Новая функция [checkFavoriteTracks](/reference/library?id=checkfavoritetracks).
94 | - К [replaceWithSimilar](/reference/filter?id=replacewithsimilar) добавлен параметр, позволяющий удалить исходных исполнителей из рекомендаций.
95 |
96 | ## Версия 1.5.2
97 | - Новые функции [getSavedAlbums](/reference/source?id=getsavedalbums), [followPlaylists](/reference/library?id=followPlaylists), [unfollowPlaylists](/reference/library?id=unfollowPlaylists).
98 | - Функция [getFollowedTracks](/reference/source?id=getfollowedtracks) получила параметр `isFlat`.
99 | - Из модуля `Yandex` удалены функции `getTracks` и `getAlbums`. Поскольку Яндекс перестал отвечать на такие запросы от Apps Script.
100 |
101 | ## Версия 1.5.1
102 | - Новый модуль [Player](/reference/player). Нужно [обновить права доступа](/tuning?id=Обновить-права-доступа).
103 | - `getPlayback` перемещен в `Player`.
104 | - Добавлена функция [Player.transferPlayback](/reference/player?id=transferplayback)
105 | - `removeTracks` и `removeArtists` получили режим проверки только основного исполнителя трека.
106 | - Изменен формат входных параметров у [replaceWithSimilar](/reference/filter?id=replacewithsimilar).
107 | - Исправлена ошибка с `getTop*`. Spotify не допускает в нем параметр `locale`. Пишите на форум если встретите ошибку `invalid request`.
108 |
109 | ## Версия 1.5.0
110 | - Учитывать всех исполнителей трека в фильтр-функциях `remove*`, `match*`, `dedup*`.
111 | - [getArtistsTracks](/reference/source?id=getartiststracks) и [getArtistsAlbums](/reference/source?id=getartistsalbums) получили параметр `isFlat`, позволяющий сгруппировать результат по исполнителю. По аналогии с [getArtistsTopTracks](/reference/source?id=getartiststoptracks). Поведение по умолчанию не изменилось, код менять не нужно.
112 | - `rangeTracks` может фильтровать по типу альбома.
113 | - `dedupTracks` получил новый аргумент для контроля отклонения продолжительности у треков с идентичными названиями. Подробнее [здесь](https://github.com/Chimildic/goofy/discussions/116).
114 | - Все get-запросы из `SpotifyRequest` содержат параметр локали ([подробнее](https://github.com/Chimildic/goofy/discussions/79#discussioncomment-1019029)).
115 |
116 | ## Версия 1.4.9
117 | - Эксперимент с функцией [sendMusicRequest](/reference/search?id=sendmusicrequest)
118 | - Новый параметр в [Lastfm.getCustomTop](/reference/lastfm?id=getcustomtop), новая функция [Lastfm.convertToSpotify](/reference/lastfm?id=converttospotify) и шаблон с их использованием - [исполнители одного хита](/template?id=Исполнители-одного-хита).
119 | - Удалена функция `getPlayingTrack`. Взамен добавлена [getPlayback](/reference/player?id=getplayback). Для использования нужно [обновить права доступа](/tuning?id=Обновить-права-доступа).
120 | - `doGet` адаптирован под открытие `launch.html`, больше не нужно заменять функцию для [управления с телефона](https://github.com/Chimildic/goofy/discussions/9)
121 | - Модифицирована функция `replaceWithSimilar`. Не должна создать дубликатов, запрашивает рекомендации с большим количеством данных от исходного трека.
122 | - Багфикс [Alisa #99](https://github.com/Chimildic/goofy/discussions/99#discussioncomment-973227)
123 |
124 | ## Версия 1.4.8
125 | - В [rangeTracks](/reference/filter?id=rangetracks) появились [вычисляемые значения](https://github.com/Chimildic/goofy/discussions/87): `anger`, `happiness` и `sadness`. Их нет со стороны Spotify, поэтому нельзя использовать при запросе рекомендаций.
126 | - Все функции match теперь проверяют и основного исполнителя трека, помимо альбома и названия.
127 | - У [getCustomTop](/reference/lastfm?id=getcustomtop) появились параметры минимального и максимального количества прослушиваний.
128 | - Удаление дубликатов альбомов при запросе альбомов от исполнителей. Баг Spotify, иногда возвращает одинаковые альбомы (для разных стран).
129 | - Добавлена документация для модуля Search
130 | - Нужно [обновить файл config](https://github.com/Chimildic/goofy/blob/main/config.js):
131 | - Добавлен параметр локали `RU` при запросе плейлистов. В связи с [постом](https://github.com/Chimildic/goofy/discussions/79#discussioncomment-814744): кириллическое название возвращалось на латинице.
132 | - Предел сохраняемой истории в 20 тысяч треков вынесен в конфиг, чтобы изменить и не терять при обновлении. Есть пример стабильной работы и с 40 тысячами.
133 |
134 | ## Версия 1.4.7
135 | - Эксперимент. При неизвестной ошибке со стороны Google во время записи через `Cache.write` происходит повторная попытка записи после паузы.
136 | - [06.06.21]
137 | - [replaceWithSimilar](/reference/filter?id=replacewithsimilar) может принять сразу несколько массивов для замены. Если замены не нашлось, трек удаляется.
138 | - [20.05.21]
139 | - Эксперимент. Дополнительная проверка при поиске наилучшего совпадения (совпадение строк), [подробнее](https://github.com/Chimildic/goofy/discussions/64).
140 | - Order.sort может сортировать массивы исполнителей и альбомов.
141 | - Новая функция [Playlist.removeTracks](/reference/playlist?id=removetracks).
142 | - [getTracks](/reference/source?id=gettracks) может выбирать ограниченное количество треков из плейлистов.
143 |
144 | ## Версия 1.4.6
145 | - Новая функция _getPlayingTrack_. Требуется [обновить права доступа](/tuning?id=Обновить-права-доступа).
146 | - При создании плейлиста можно указать статичную обложку через прямую ссылку на нее.
147 |
148 | ## Версия 1.4.5
149 | - Теперь [mineTracks](/reference/source?id=minetracks) может искать ключевые слова в названиях альбомов и самих треках.
150 | - В `mineTracks` аргумент `playlistCount` **переименован** в `itemCount`.
151 | - Новая функция у Filter: [replaceWithSimilar](/reference/filter?id=replacewithsimilar).
152 | - Новая функция у Lastfm: [getSimilarArtists](/reference/lastfm?id=getsimilarartists).
153 |
154 | ## Версия 1.4.4
155 | - Новый фильтр [removeUnavailable](/reference/filter?id=removeunavailable).
156 | - `Cache` может читать/писать файлы с расширением `.txt` при явном указании в имени файла.
157 | - `getCustomTop` поддерживает тип `Date`, [подробнее](https://github.com/Chimildic/goofy/discussions/46#discussioncomment-351974).
158 | - Исправление логической ошибки в `match` при отборе.
159 |
160 | ## Версия 1.4.3
161 | - Теперь [getCustomTop](/reference/lastfm?id=getcustomtop) может составить топ по альбомам.
162 | - При сортировке по дате релиза, треки сохраняют оригинальный порядок в рамках своего альбома, если изначально были в таком порядке
163 | - Багфиксы
164 |
165 | ## Версия 1.4.2
166 | - Теперь [craftTracks](/reference/source?id=crafttracks) может принимать статичные `seed_*` отличные от `key`.
167 | - Новая функция к Lastfm: [getCustomTop](/reference/lastfm?id=getcustomtop).
168 | - Новая функция к Selector: [pickYear](/reference/selector?id=pickyear).
169 | - Новая функция к Order: [separateYears](/reference/order?id=separateyears).
170 | - Улучшение для поиска. Если один и тот же элемент присутствует в массиве несколько раз (то есть имеет одинаковое ключевое слово для поиска), будет затрачен только один запрос поиска.
171 | - Функции `Source`, связанные с плейлистами, добавляют к каждому треку объект `origin`, содержащий `name` и `id` плейлиста-источника.
172 | - Предпринята попытка продолжить исполнение кода после получения исключения `Exception: Адрес недоступен`, [подробнее](https://github.com/Chimildic/goofy/discussions/27).
173 |
174 | ## Версия 1.4.1
175 | - Ускорено время выполнения функции `craftTracks`.
176 | - Найдена недокументированная возможность Spotify API. Функция [getRecomTracks](/reference/source?id=getrecomtracks) поддерживает ключ `popularity`. В связи с этим он **удален** у [craftTracks](/reference/source?id=crafttracks). Переместите его в параметр `query`, если использовали.
177 | - К `Order.sort` добавлена возможность сортировки по дате релиза альбома, которому принадлежит трек.
178 | - Из списка функций, которым можно задать триггер скрыты `displayAuthResult`, `updateRecentTracks`, `logProperties`.
179 |
180 | ## Версия 1.4.0
181 | - **Удалена** функция `Source.getRecentTracks`. Используйте `RecentTracks.get` или `Cache.read` для нужного файла истории.
182 | - Новые функции к Source: [mineTracks](/reference/source?id=minetracks), [craftTracks](/reference/source?id=crafttracks).
183 | - Новая функция к RecentTracks: [appendTracks](/reference/recenttracks?id=appendtracks).
184 | - Структура файла `SpotifyRecentTracks` обновлена до обычного массива треков (как у остальных файлов истории). Обновление произойдет автоматически при первом запуске триггера. До этого момента `Cache.read` будет возвращать старую структуру.
185 | - К Library добавлены функции сохранения и удаления альбомов библиотеки.
186 |
187 | ## Версия 1.3.4
188 | - Новые функции к Source: [getCategoryTracks](/reference/source?id=getcategorytracks), [getListCategory](/reference/source?id=getlistcategory).
189 | - Появился параметр [REQUESTS_IN_ROW](/config).
190 | - При чтении пустого файла через Cache.read выбрасывается исключение, чтобы предотвратить перезапись файла при баге со стороны Google ([подробнее](https://github.com/Chimildic/goofy/discussions/26)).
191 | - Новая функция [Playlist.saveWithUpdate](/reference/playlist?id=savewithupdate).
192 | - Функции match* могут принимать массив исполнителей. В случае массива треков, сравнение по названию трека и альбома (без исполнителя). В случае массива исполнителей, только его имя.
193 | - В документацию добавлены шаблоны с форума (Назад в этот день, исполнитель дня)
194 |
195 | ## Версия 1.3.3
196 | - Оптимизация запросов к Last.fm в механизме накопления. Поиск только тех треков, что являются новыми для истории прослушиваний.
197 | - Новые функции к Lastfm: [getSimilarTracks](/reference/lastfm?id=getsimilartracks), [getTopArtists](/reference/lastfm?id=gettopartists-1), [getTopAlbums](/reference/lastfm?id=gettopalbums).
198 | - Новые функция к Source: [getRelatedArtists](/reference/source?id=getrelatedartists), [getAlbumsTracks](/reference/source?id=getalbumstracks).
199 | - Новая функция к Yandex: _getAlbums_._
200 | - Теперь [dedupArtists](/reference/filter?id=dedupartists) может удалить дубликаты из массива исполнителей.
201 | - Теперь [removeArtists](/reference/filter?id=removeartists) может удалять по массиву исполнителей.
202 | - Корректировка группы методов match*
203 | - При поиске и сравнивании из строки удаляются специальные символы (,!@# и тд).
204 | - Более информативные сообщения в логах для истории прослушиваний и при поиске.
205 |
206 | ## Версия 1.3.2
207 | - Обновлен механизм отправки запросов. Многие функции-источники стали отрабатывать быстрее за счет асинхронной отправки сразу N-количества запросов.
208 | - Дополнение функции mixin. Теперь можно задавать соотношение более, чем двум массивам. Подробнее в [mixinMulti](/reference/combiner?id=mixinmulti).
209 | - Новые функции: [getTopArtits](/reference/lastfm?id=gettopartists), [getArtistsTopTracks](/reference/lastfm?id=getartiststoptracks).
210 |
211 | ## Версия 1.3.1
212 | - Новые функции для модуля Cache: [rename](/reference/cache?id=rename), [remove](/reference/cache?id=remove), [clear](/reference/cache?id=clear), [compressArtists](/reference/cache?id=compressArtists).
213 | - Стали публичными функции: [getArtists](/reference/source?id=getartists), [getArtistsAlbums](/reference/source?id=getartistsalbums), [getAlbumTracks](/reference/source?id=getalbumtracks).
214 | - Функция getTracksArtists **переименована** в getArtistsTracks.
215 | - Повторный вызов getSavedTracks в том же скрипте отправляет новые запросы к Spotify, вместо возврата ранее полученного. Используйте [sliceCopy](/reference/selector?id=slicecopy) для создания копии.
216 | - Количество отправленных запросов теперь получается через `CustomUrlFetchApp.getCountRequest`.
217 | - Багфикс: spotify get с 404 прерывал скрипт; lastfm с ошибками 500+ прерывал скрипт.
218 | - Багфикс: separateArtists не разделял исполнителей.
219 | - Множество небольших правок.
220 |
221 | ## Версия 1.3.0
222 | - Обновлены: инструкция и видео по установке.
223 | - Функциям `removeTracks` и `removeArtists` добавлен аргумент `invert` (инверсия).
224 | - Подавление ошибок от lastfm, чтобы не прерывать исполнение скрипта.
225 | - Добавлено анонимное отслеживание распределения версий библиотеки через Google Forms. Отправляются значения версии и идентификатор скрипта. Чтобы иметь представление каково количество уникальных пользователей.
226 |
227 | ## Версия 1.2.0
228 | - Добавлены `параметры` для отслеживания истории. Нужно сделать [миграцию](https://4pda.ru/forum/index.php?act=findpost&pid=102495416&anchor=migrate_params).
229 | - Лимит истории прослушиваний увеличен с 10 до 20 тысяч.
230 | - Трекам истории Lastfm добавляется дата прослушивания. Можно использовать `rangeDateRel`.
231 | - Механизм накопления прослушиваний Lastfm, если установить `параметры`. Чтобы вместо `Lastfm.getRecentTracks` с малым числом треков из-за лимитов, получать много и быстро.
232 | - Получать историю одной функцией `RecentTracks.get`, не зависимо от `параметров`, в том числе сводную из двух источников. В сводной удалены дубликаты, есть сортировка от свежих к старым прослушиваниям.
233 |
--------------------------------------------------------------------------------
/docs/config.md:
--------------------------------------------------------------------------------
1 | # Параметры
2 |
3 | Описание параметров из файла `config`
4 |
5 | ## API
6 | - `CLIENT_ID` и `CLIENT_SECRET` (строка) - ключи для доступа к Spotify Web API. Создаются при [первой установке](/install).
7 |
8 | - `LASTFM_API_KEY` (строка) - ключ для работы с API Last.fm. Создается [дополнительно](/tuning?id=Настройка-lastfm).
9 |
10 | - `MUSIXMATCH_API_KEY` (строка) - ключ от сервиса musixmatch для работы функции [detectLanguage](/reference/filter?id=detectlanguage). Создается [дополнительно](/tuning?id=Настройка-musicmatch).
11 |
12 | ## История прослушиваний
13 | - `ON_SPOTIFY_RECENT_TRACKS` (булево) - при `true` отслеживание истории прослушиваний Spotify. При `false` отключается.
14 |
15 | - `ON_LASTFM_RECENT_TRACKS` (булево) - при `true` отслеживание истории прослушиваний Last.fm. При `false` отключается.
16 |
17 | - `LASTFM_LOGIN` (строка) - логин пользователя Last.fm, чья история собирается. Используется по умолчанию и в других функциях модуля.
18 |
19 | - `LASTFM_RANGE_RECENT_TRACKS` (число) - количество последних треков, которые просматриваются в истории Last.fm за прошедшие 15 минут.
20 |
21 | - `COUNT_RECENT_TRACKS` (число) - количество сохраняемых треков истории. По умолчанию 20 тысяч. На практике работает нормально и с 40 тысячами. Предел это объем файла в 50 мб.
22 |
23 | ## Общее
24 | - `LOG_LEVEL` (строка) - при `info` выводятся сообщения с информацией и ошибками от функций библиотеки. При `error` только сообщения об ошибках. При пустой строке отключает сообщения. В параметрах `config` задается значение по умолчанию, действующие при каждом запуске. В своем коде можно изменить уровень логов на время текущего выполнения `Admin.setLogLevelOnce('значение')`.
25 |
26 | - `LOCALE` (строка) - локаль при запросе плейлистов. Влияет на то, в каком виде представляются названия треков. [Известны случаи](https://github.com/Chimildic/goofy/discussions/79#discussioncomment-814744), когда исполнитель на кириллице возвращался с аналогом на латинице. Значение по умолчанию `RU`.
27 |
28 | - `REQUESTS_IN_ROW` (число) - количество параллельно отправляемых запросов, когда это возможно. По умолчанию 40. Влияет на скорость получения данных. Например, запрос треков плейлиста. При получении большего количества ошибок номер `503` или наличию алгоритмов с очень большим числом запросов, рекомендуется снизить значение данного параметра. Повышение не рекомендуется.
29 |
30 | - `MIN_DICE_RATING` (число) - минимальное значение коэффициента от 0.0 до 1.0, при котором элемент считается наилучшим совпадением при импорте, например, треков в Spotify. По умолчанию _0.6005_.
31 | Если найденный элемент будет иметь меньшее значение, он отбрасывается. Когда несколько элементов удовлетворяют минимальному значению, выбирается элемент с наибольшим из них.
--------------------------------------------------------------------------------
/docs/details.md:
--------------------------------------------------------------------------------
1 | # Принцип работы
2 |
3 | goofy это конструктор с набором функций на JavaScript. С их помощью составляется алгоритм, который автоматизирует различные задачи. К примеру, найти любимые треки, которые давно не прослушивались.
4 |
5 | Выполнение алгоритма происходит на стороне платформы Google Apps Script. То есть задачи выполняются по расписанию без вашего участия.
6 |
7 | ?> При установке или изменении версии, отправляется единичный запрос в Google Формы. Чтобы иметь представление сколько пользователей активно используют goofy.
8 |
9 | ## Отличия от Smarter Playlists
10 |
11 | Главное отличие заключается в способе составления алгоритма. В Smarter Playlists визуальный язык, диаграмма. В случае goofy используется язык программирования.
12 |
13 | Пример следующего алгоритма в Smarter Playlists: взять треки из двух плейлистов, выполнить случайную сортировку, сохранить первые 50 треков в новый плейлист.
14 |
15 | 
16 |
17 | Теперь тоже самое с помощью goofy
18 | ```js
19 | let tracks = Source.getTracks([
20 | { name: 'Микс дня 1', id: '123' },
21 | { name: 'Микс дня 2', id: '456' },
22 | ]);
23 |
24 | Order.shuffle(tracks);
25 |
26 | Playlist.saveAsNew({
27 | name: 'Личный микс дня',
28 | tracks: Selector.sliceFirst(tracks, 50),
29 | });
30 | ```
31 |
32 | ## История прослушиваний
33 |
34 | goofy автоматически отслеживает историю прослушиваний. При достижении предела (60 тысяч), новые прослушивая по-прежнему сохраняются за счет удаления самых старых. Процесс отслеживания начинается сразу после завершения настройки. Прослушивания, произошедшие до настройки не попадут в список. Однако есть способ добавить их вручную, при наличии last.fm.
35 |
36 | ?> В новый аккаунт last.fm можно [добавить](https://support.last.fm/t/how-to-add-scrobbles-history-from-spotify-to-last-fm/40038) историю прослушиваний, которая была до создания аккаунта.
37 |
38 | У Spotify API есть функция для получения истории прослушиваний. Но *максимум* 50 последних треков, которые прослушаны более чем 30 секунд. В историю не попадают подкасты и треки, прослушанные в приватном режиме.
39 |
40 | Возможность goofy отслеживать историю появляется за счет платформы Apps Script и её доступа к Google Drive. Каждые 15 минут goofy обращается к Spotify и обновляет содержимое файла на диске новыми данными.
41 |
42 | Теоретически, лимит истории можно увеличить до 100 тысяч. Но возникает вопрос производительности в рамках лимитов платформы Apps Script, доступности дискового пространства и в целом целесообразности такого массива. Поскольку в большинстве случаев такая история нужна для удаления треков из вновь создаваемых плейлистов. Задачи по составлению топа легко решаются другими средствами при наличии Last.fm.
43 |
44 | Last.fm способен сам отслеживать прослушивания Spotify (подключение в [профиле](https://www.last.fm/settings/applications)). Но все перечисленное не позволяет перехватить быстрые пропуски треков. Для этого есть два решения:
45 |
46 | - Первое подробно [описано на форуме](https://github.com/Chimildic/goofy/discussions/53).
47 | - Второе заключается в том, чтобы отключить отслеживание Last.fm и использовать сторонние программы для ПК и смартфона, которые позволяют задать промежуток в несколько секунд, после которых трек сохраняется в историю. Например, [Pano Scrobbler](https://4pda.to/forum/index.php?showtopic=887068).
48 |
49 | На практике Spotify API работает нестабильно. Трек после 30 секунд может вернуться с задержкой или совсем потеряться. Проблема со стороны Spotify, которая никак не решается. Однако вместе с Last.fm это становится совсем незначительным.
50 |
51 | Если вы часто слушаете скачанные треки без подключения к интернету, используйте сторонние скробблеры, как в примере выше. Они позволяют сохранять прослушивания локально и убрать зависимость от офлайна и сбоев Spotify. Если офлайн прослушивания это редкий сценарий, достаточно подключения через профиль.
52 |
53 | ## Ограничения
54 |
55 | ?> При первом знакомстве некоторые детали могут быть непонятны
56 |
57 | Библиотека подчиняется ограничениям со стороны платформ. Ниже описание конкретных показателей и на что они влияют на основе справочной информации, предлагаемой платформами.
58 |
59 | ### Apps Script {docsify-ignore}
60 | - Выполнение скрипта (6 минут / одно выполнение)
61 |
62 | - Общая максимальная продолжительность *одного* запуска скрипта. Как правило, легкие шаблоны завершаются за считанные секунды. Приблизиться к минуте или нескольким можно в случае большого объема входных и/или выходных данных.
63 | - Например, функция [getFollowedTracks](/reference/source?id=getfollowedtracks) для пользователя [spotify](https://open.spotify.com/user/spotify) и аргументу `owned` в среднем отрабатывает за 4 минуты. При этом получая 1.4 тысячи плейлистов и 102 тысяч треков. После удаления дубликатов остается 78 тысяч.
64 | - Если для 78 тысяч вызвать [rangeTracks](/reference/filter?id=rangetracks) лимит 6 минут будет превышен. Но предварительно отбросив заранее неподходящие треки, например, с помощью [rangeDateRel](/reference/filter?id=rangedaterel), [match](/reference/filter?id=match) и прочего, можно существенно и быстро снизить количество треков.
65 |
66 | - Количество запросов (20 тысяч / день)
67 |
68 | - Как правило, 1 запрос к Spotify это получение 50 плейлистов или 50 треков. В некоторых случаях 100.
69 | - Пример выше получил 1.4 тысяч плейлистов и 102 тысячи треков за 1 735 запросов.
70 | - На получение 11 тысяч треков плейлиста 110 запросов и 25 секунд. Примерно столько же на создание плейлиста с таким количеством треков.
71 | - На получение 10 тысяч любимых треков уйдет 200 запросов.
72 | - В целом, сложно представить функцию на 20 тысяч запросов из-за ограничения 6 минут на выполнение. По этой причине можно сказать, что нельзя обойти все плейлисты роботов-пользователей с тысячами плейлистов. Но личный профиль или средних авторов можно.
73 |
74 | - Выполнение триггеров (90 минут / день)
75 |
76 | - Общая максимальная продолжительность выполнения триггеров. Единственный способ достичь предела это вызвать 15 раз функцию на 6 минут за один день. Сложно представить задачу, которая потребует этого и оправдает себя.
77 |
78 | - Количество триггеров (20 / пользователь / скрипт)
79 |
80 | - При грубом описании это 20 плейлистов, которые создаются по совершенно *разному* расписанию.
81 | - На практике, несколько функций можно вызывать из одной другой функции, что позволяет создать N плейлистов за один триггер. Подробнее [здесь](/best-practices?id=Экономика-триггеровв).
82 | - Кроме того, можно создать еще одну копию библиотеки и также получить квоту на 20 триггеров.
83 |
84 | > Если вам потребовалось создать еще одну копию, можете повторно использовать значения CLIENT_ID и CLIENT_SECRET и не создавать новое приложение на стороне Spotify.
85 |
86 | Остальные ограничения Apps Script не относятся к библиотеке. Они связаны с почтой, таблицами и прочими сервисами. Или недостижимы из-за ограничений Web API Spotify. Подробнее [здесь](https://developers.google.com/apps-script/guides/services/quotas).
87 |
88 | ### Web API Spotify {docsify-ignore}
89 | - Локальные файлы игнорируются. [API не позволяет](https://developer.spotify.com/documentation/general/guides/local-files-spotify-playlists/) добавлять такие треки в новые плейлисты и практически не несут в себе данных для фильтрации, сортировки.
90 |
91 | > It is not currently possible to add local files to playlists using the Web API, but they can be reordered or removed.
92 |
93 | - Количество треков
94 | - При добавлении в плейлист до 11 тысяч треков.
95 | - При получении с одного плейлиста также 11 тысяч.
96 | - Любимые треки до 20 тысяч.
97 | - При фильтрации, сортировке, выборе количество неограниченно. Но в пределах квоты Apps Script.
98 | - Количество плейлистов
99 | - Теоретически до 11 тысяч, но не хватит квоты Apps Script на получение треков с них. Реальное значение в пределах 2 тысяч. Зависит от общего количества треков.
100 | - Количество запросов
101 | - Точного числа нет. При слишком большом объеме запросов за короткий промежуток времени могут появиться ошибки 500, 503 и подобное. Проходят после паузы.
102 |
103 | ### Google Диск {docsify-ignore}
104 | - Размер одного текстового файла ограничен - 50 мб. Для сокращения объема можно использовать функцию [Cache.compressTracks](/reference/cache?id=compresstracks). Экспериментально удалось создать файл со 100 тысячами **сжатых** треков и уложиться в 50 мб.
105 |
106 |
--------------------------------------------------------------------------------
/docs/errors.md:
--------------------------------------------------------------------------------
1 | # Ошибки
2 |
3 | Если на странице нет вашей ошибки, напишите в [телеграм](https://t.me/forum_goofy) или на [форум](https://github.com/Chimildic/goofy/discussions). Подробно опишите ваши действия и добавьте код.
4 |
5 | ## Apps Script
6 |
7 | | Ошибка | Решение |
8 | |-|-|
9 | | Access not granted or expired | Нет доступа к Spotify. Часто возникает из-за смены пароля. Пройдите авторизацию повторно (`начать развертывание` - `пробное развертывание` - `веб-приложение`). |
10 | | Exceeded maximum execution time | Время выполнения функции [превысило 6 минут](/details?id=Ограничения). Когда каждый запуск выполняется несколько минут, пересмотрите алгоритм, проведите оптимизацию согласно примечаниям документации и [полезному опыту](/best-practices). В редких случаях наблюдается у функций, которые обычно отрабатывают за считанные секунды. Пока объясняется лагом платформы, поскольку невозможно воспроизвести ошибку для отладки. |
11 | | Limit exceeded | Превышен один из лимитов платформы. В классическом варианте, ограничения описаны [здесь](/details?id=Ограничения). Если экспериментируете с другими возможностями платформы, есть и [другие](https://developers.google.com/apps-script/guides/services/quotas). |
12 | | Service invoked too many times Service using too much computer time for one day | Частный случай ранее описанных ошибок. Точная разница неизвестна. В лучшем случае, пройдет после короткой паузы. В худшем, по обновлению квоты, то есть через сутки. |
13 | | Client ID is required | В сохраненных параметрах отсутствует Client ID от Spotify. Вызовите функцию `setProperties` из файла `config`. При этом у параметров `CLIENT_ID` и `CLIENT_SECRET` должны быть значения из кабинета разработчика Spotify (не текст `вашеЗначение`) |
14 | | Address unavailable | Причина возникновения неизвестна. При возникновении автоматически выжидается пауза и отправляется повторный запрос. Как правило, со второй попытки ошибки не возникает. |
15 | | Ошибка службы: Диск | Причина возникновения неизвестна. С версии 1.5.4 предпринимается попытка поймать ошибку и повторить операцию. |
16 | | ReferenceError: Cheerio is not defined | Добавьте библиотеку [Cheerio](https://github.com/Chimildic/goofy/discussions/91#discussioncomment-1931923). |
17 |
18 | ## Запросы
19 |
20 | | Статус | Описание |
21 | |-|-|
22 | | 400 | Сервер получил некорректный запрос. Напишите на форум или в телеграм, если ошибка стабильно воспроизводится. |
23 | | 401 403 | Запрос составлен корректно, но отсутствует доступ. [Обновите права доступа](/tuning?id=Обновить-права-доступа). Обратите внимание, что функции [Player](/reference/player) доступны только с активной подпиской (ограничение от Spotify). |
24 | | 404 | Запрашиваемые данные не найдены. Например, указано несуществующее `id` плейлиста. |
25 | | 413 | Известен случай возникновения при загрузке обложки плейлиста. При кодировании размер данных превысил 256 кб. Поэтому Spotify отклонил запрос. Используйте другую обложку. При `randomCover` со значением `update`, обложка обновится в следующий раз. |
26 | | 429 | За единицу времени совершено много запросов. Сервер перестал отвечать контентом. Как правило, автоматически выжидается пауза в несколько секунд и процесс возобновляется. |
27 | | 500 503 | Внутренняя ошибка на стороне сервера Spotify или Last.fm. В обычном случае, пропадет через несколько минут. Во время критических сбоев может продолжаться неопределенное время. Со своей стороны ничего исправлять невозможно. |
28 | | 504 | Превышено время ожидания ответа от сервера. Будет попытка повторить запрос. |
--------------------------------------------------------------------------------
/docs/first-playlist.md:
--------------------------------------------------------------------------------
1 | ## Основные страницы
2 | - [Проекты](https://script.google.com/home/my) - перечень проектов на вашем аккаунте, при выборе проекта открывается _редактор кода_
3 | - [Выполнения](https://script.google.com/home/executions) - содержит список с _результатами_ выполненных функций
4 | - [Триггеры](https://script.google.com/home/triggers) - расписание для выполнения функций
5 |
6 | ## Создание плейлиста
7 |
8 | Выполним следующую задачу:
9 | - возьмем треки из плейлистов `микс дня`
10 | - удалим из них любимые треки (лайки)
11 | - добавим рекомендации
12 | - создадим плейлист
13 | - зададим расписание (триггер)
14 |
15 | 1. Откройте файл `main` в _редакторе кода_ и вставьте в него следующий код и __сохраните__ (иконка дискеты или CtrlS):
16 | ```js
17 | function createFirstPlaylist() {
18 | // 1 - собираем
19 | let mixTracks = Source.getTracks([
20 | { name: 'Микс дня 1', id: 'вашеId' },
21 | { name: 'Микс дня 2', id: 'вашеId' },
22 | ]);
23 | let recomTracks = Source.craftTracks(mixTracks);
24 | let savedTracks = Source.getSavedTracks();
25 |
26 | // 2 - обрабатываем
27 | Combiner.push(mixTracks, recomTracks);
28 | Filter.removeTracks(mixTracks, savedTracks);
29 | Selector.keepRandom(mixTracks, 20);
30 |
31 | // 3 - создаем плейлист
32 | Playlist.saveWithReplace({
33 | // id: 'вашеId',
34 | name: 'Первый плейлист',
35 | tracks: mixTracks,
36 | randomCover: 'update',
37 | });
38 | }
39 | ```
40 |
41 | 2. Логическая структура кода состоит из трех блоков. Сначала запрашиваются треки. Затем над ними проводятся операции. И наконец команда создать плейлист с получившимся набором треков.
42 |
43 | Воспринимайте каждую строчку как команду к действию что-то сделать. Такие команды называют _функцией_. Функцию можно _определить_ (создать) и _вызвать_. То есть задать алгоритм и выполнить по нашему запросу. В данном случае, создается функция `createFirstPlaylist`, которая внутри себя вызывает функции библиотеки Goofy. Совокупность команд (вызовов функций), образует код.
44 |
45 | Чтобы понимать что делает код, просто читайте его как книжку. У каждого слова есть определение в словаре. Так и здесь, у каждой функции есть описание того, что она делает. Зачастую основной смысл передает само имя функции. Поэтому знание английского или использование переводчика во многом поможет. Однако всегда есть нюансы: что передать в функцию, что она сделает, что вернет?
46 |
47 | Описание функций доступно в _списке функций_. К примеру, найдем описание функции `Source.getTracks`. В левом меню нажмите правой кнопкой мыши на пункт `список модулей` и откройте страницу в новой вкладке. Перейдите на эту вкладку и найдите `Source`, а затем `getTracks`. Увидим описание и примеры использования.
48 |
49 | 3. Сейчас код сработает с ошибкой. Потому что не задано `id` плейлистов `микс дня`.
50 |
51 | Чтобы получить `id`, нужно зайти на страницу плейлиста в Spotify и нажать `поделиться`. Так получим ссылку на плейлист. Из нее нужно достать `id`.
52 |
53 | > Если открыть меню `поделиться` и нажать кнопку `Alt`, Spotify предложит скопировать URI, а не ссылку.
54 |
55 | 
56 |
57 | Перейдите на [эту страницу](/reference/desc) в новой вкладке, чтобы увидеть как достать `id` из ссылки или URI.
58 |
59 | Зайдите на страницу плейлиста `Микс дня` (Раздел Поиск - Для тебя - Микс дня), скопируйте ссылку, вставьте в код и удалите все лишнее, оставив только `id`. Повторите для второго плейлиста.
60 |
61 | В результате получится следующее
62 | ```js
63 | let mixTracks = Source.getTracks([
64 | { name: 'Микс дня 1', id: '491ZfFnGxaBF445JOhhxiO' },
65 | { name: 'Микс дня 2', id: '426ZfFnGxaBF445JfOJefE' },
66 | ]);
67 |
68 | // Ошибки!
69 | // { name: 'Микс дня 1', id: 'https://open.spotify.com/playlist/491ZfFnGxaBF445JOhhxiO?si=343F7972b107494a' },
70 | // { name: 'Микс дня 1', id: 'spotify:playlist:426ZfFnGxaBF445JfOJefE' },
71 | ```
72 |
73 | 4. Остальной код не требует настройки. На панели управления выберите нашу функцию и нажмите `выполнить`.
74 |
75 | 
76 |
77 | > Возможно увидите ошибку с номером 500. Не обращайте внимание. Иногда Spotify плохо отвечает на запрос о рекомендациях.
78 |
79 | 5. Зайдите в Spotify. У вас появился плейлист с названием `Первый плейлист`. В нем нет треков с лайками. При этом некоторые треки пришли из рекомендаций, а некоторые из миксов дня. Такая неопределенность возникла намеренно. Потому что в коде вызывается функция `Selector.keepRandom`, которая делает случайный отбор указанного количества треков.
80 |
81 | Найдите в коде строчку с функцией `Combiner.push`. И поставьте в начале строки символы `//`
82 |
83 | ```js
84 | // Combiner.push(mixTracks, recomTracks);
85 | ```
86 |
87 | Вы _закомментировали_ строку. Любой текст после знаков `//` игнорируются, не будет вызываться.
88 |
89 | Теперь найдите строчки `let recomTracks` и `Selector.keepRandom` - закомментируйте их тоже.
90 |
91 | Что сделали? Перестали запрашивать рекомендации, поэтому не объединяем их с треками из миксов и убрали случайный отбор.
92 |
93 | 6. Запустите функцию еще раз. Теперь в `Первом плейлисте` будут только треки из `миксов дня`, из которых удалены лайки.
94 |
95 | Обратите внимание, что не создался новый плейлист, а обновился существующий. Благодаря функции `Playlist.saveWithReplace`.
96 |
97 | Однако существует важный нюанс. Функция произвела поиск среди ваших плейлистов по имени `Первый плейлист`, чтобы получить `id` и отправить запросы для добавления треков. Теоретически, в вашей фонотеке может существовать несколько плейлистов с __одинаковыми__ именами. Поиск не сможет определиться в какой конкретно плейлист вы хотите добавить треки. Поэтому возьмет первый, который найдется. Что может быть нежелательным, привести к ошибке, обновлению не того плейлиста.
98 |
99 | Чтобы этого избежать, после первого запуска функции, вернитесь к коду, чтобы явно указать `id` плейлиста. То есть точно также зайдите на страницу плейлиста, скопируйте ссылку и вставьте `id` в код. Куда? В перечень аргументов функции, связанной с созданием плейлиста.
100 |
101 | В нашем примере, вам нужно удалить знак комментария `//` у `id` и вставить само значение
102 | ```js
103 | // Было
104 | Playlist.saveWithReplace({
105 | // id: 'вашеId',
106 | name: 'Первый плейлист',
107 | tracks: mixTracks,
108 | randomCover: 'update',
109 | });
110 |
111 | // Стало
112 | Playlist.saveWithReplace({
113 | id: '476ZfFnGxaBF4В5JАhhxiO',
114 | name: 'Первый плейлист',
115 | tracks: mixTracks,
116 | randomCover: 'update',
117 | });
118 | ```
119 |
120 | 7. Мы создали `Первый плейлист` благодаря функции `createFirstPlaylist`. Теперь научимся создавать расписание для автоматического запуска функций с помощью триггеров.
121 |
122 | Триггеру нужно задать время запуска. Например, каждый день в семь утра или каждый понедельник. А также имя функции, которую нужно запускать.
123 |
124 | Одновременно с этим, затронем вопрос того, как создать сразу несколько плейлистов.
125 | Сейчас в файле `main` у вас одна функция. Добавьте _в конец файла_ новую функцию (не забудьте сохранить файл):
126 | ```js
127 | function createRandomSavedTracks() {
128 | let tracks = Source.getSavedTracks();
129 |
130 | Selector.keepRandom(tracks, 5);
131 |
132 | Playlist.saveWithReplace({
133 | name: 'Случайные любимые треки',
134 | tracks: tracks,
135 | });
136 | }
137 | ```
138 |
139 | ?> В вашем проекте три файла. `library` содержит функции библиотеки Goofy. Вы используете их для создания плейлистов. Файл `config` содержит настройки. В `main` расположен ваш код. Поскольку вы можете создать (скопировать) множество функций, имеет смысл создавать новые файлы для логического разделения. Чтобы в `main` не накапливалась огромная стена кода.
140 |
141 | 8. В левом меню выберите триггеры
142 |
143 | 
144 |
145 | Справа внизу кнопка `Добавление триггера`
146 | - Выберите функцию `createRandomSavedTracks`
147 | - Триггер по времени
148 | - По минутам
149 | - Раз в минуту
150 | - Сохраните
151 |
152 | 9. Зайдите в Spotify. Через минуту появится новый плейлист и каждую следующую минут его содержание будет обновляться 5 случайными любимыми треками.
153 |
154 | После проверки этого, перейдите в [список триггеров](https://script.google.com/home/triggers). Вы увидите два триггера: для функции `createRandomSavedTracks`, созданный вручную ранее и для функции `runTasks_`, созданный автоматически. Подробнее о нем читать в [истории прослушиваний](/details?id=История-прослушиваний).
155 |
156 | Удалите триггер для функции `createRandomSavedTracks`: справа нажмите три точки, удалить триггер. В этом же меню можно открыть список выполнений (результатов) *конкретного* триггера.
157 |
158 | Перейдите в раздел [мои выполнения](https://script.google.com/home/executions). Вы увидите общий список завершенных или выполняемых операций, их времени работы, статуса завершения, логов.
159 |
160 | ## Продолжение работы
161 |
162 | Выше вы ознакомились с основными принципами работы. Рекомендуется продолжить знакомство через [шаблоны](/template). Они показывают базовые приемы работы с библиотекой, их проще модифицировать. Особенно если у вас нет навыков программирования.
163 |
164 | Разработку собственных алгоритмов следует начать с описания идеи. Что будет источником треков, какие операции выполнить (получить рекомендации, отфильтровать и прочее), куда сохранить, как часто обновлять. Затем найдите соответствующие функции в [документации](/reference/index). Чаще всего это модули `Source`, `Filter` и `Playlist`.
165 |
166 | ?> [На форуме](https://github.com/Chimildic/goofy/discussions) помогут разобраться с ошибкой и составить алгоритм. Всегда прикладывайте свой код.
167 |
168 | Следующим шагом станет оптимизация. Для этого ознакомьтесь с [ограничениями](/details?id=Ограничения) и [полезным опытом](/best-practices).
169 |
170 | Расширенные возможности доступны с [аддонами](/addon). Например, управление с телефона.
171 |
172 | ## Синтаксис
173 |
174 | - `Функция`
175 | ```js
176 | function myName(){
177 | // Тело функции
178 | }
179 | ```
180 |
181 | - Ключевое слово `function` обязательно.
182 | - Дальше произвольное имя, здесь это `myName`.
183 | - Круглые скобки `()` для перечисления аргументов (входные данные). Здесь нет аргументов.
184 | - Фигурные скобки `{}` определяют границу функции.
185 | - Символы `//` для написания комментария.
186 |
187 | - `Переменная`
188 | ```js
189 | let tracks = 5;
190 | tracks = 10;
191 | ```
192 |
193 | - Ключевое слово `let` обязательно при первом объявлении переменной.
194 | - Дальше произвольное имя. Здесь это `tracks`.
195 | - `= 5` присвоение переменной `tracks` значения `5`. Присвоение справа налево.
196 | - Точка с запятой `;` в данном языке необязательна. Но желательно для избежания сложных ошибок.
197 | - На второй строке присвоение 10. Значение 5 теряется.
198 |
199 | - Использование `функции` и `переменной`
200 | ```js
201 | Module.myName(tracks);
202 | ```
203 |
204 | - Вызов функции `myName` из модуля `Module` и передача переменной `tracks`.
205 |
206 |
--------------------------------------------------------------------------------
/docs/guide.md:
--------------------------------------------------------------------------------
1 | # Дополнительно
2 |
3 | # Параметры
4 |
5 | Описание параметров из файла `config`
6 |
7 | ## API
8 | - `CLIENT_ID` и `CLIENT_SECRET` (строка) - ключи для доступа к Spotify Web API. Создаются при [первой установке](/install).
9 |
10 | - `LASTFM_API_KEY` (строка) - ключ для работы с API Last.fm. Создается [дополнительно](/tuning?id=Настройка-lastfm).
11 |
12 | - `MUSIXMATCH_API_KEY` (строка) - ключ от сервиса musixmatch для работы функции [detectLanguage](/reference/filter?id=detectlanguage). Создается [дополнительно](/tuning?id=Настройка-musicmatch).
13 |
14 | ## История прослушиваний
15 | - `ON_SPOTIFY_RECENT_TRACKS` (булево) - при `true` отслеживание истории прослушиваний Spotify. При `false` отключается.
16 |
17 | - `ON_LASTFM_RECENT_TRACKS` (булево) - при `true` отслеживание истории прослушиваний Last.fm. При `false` отключается.
18 |
19 | - `LASTFM_LOGIN` (строка) - логин пользователя Last.fm, чья история собирается. Используется по умолчанию и в других функциях модуля.
20 |
21 | - `LASTFM_RANGE_RECENT_TRACKS` (число) - количество последних треков, которые просматриваются в истории Last.fm за прошедшие 15 минут.
22 |
23 | - `COUNT_RECENT_TRACKS` (число) - количество сохраняемых треков истории. По умолчанию 60 тысяч. На практике работает и с большими значениями. Предел это объем файла в 50 мб.
24 |
25 | ## Общее
26 | - `LOG_LEVEL` (строка) - при `info` выводятся сообщения с информацией и ошибками от функций библиотеки. При `error` только сообщения об ошибках. При пустой строке отключает сообщения. В параметрах `config` задается значение по умолчанию, действующие при каждом запуске. В своем коде можно изменить уровень логов на время текущего выполнения `Admin.setLogLevelOnce('значение')`.
27 |
28 | - `LOCALE` (строка) - локаль при запросе плейлистов. Влияет на то, в каком виде представляются названия треков. [Известны случаи](https://github.com/Chimildic/goofy/discussions/79#discussioncomment-814744), когда исполнитель на кириллице возвращался с аналогом на латинице. Значение по умолчанию `RU`.
29 |
30 | - `REQUESTS_IN_ROW` (число) - количество параллельно отправляемых запросов, когда это возможно. По умолчанию 20. Влияет на скорость получения данных. Например, запрос треков плейлиста. Повышение не рекомендуется. Spotify стал давать теневой бан на сутки и больше (то есть перестает отвечать на запросы). Если продолжаете получать бан, уменьшите значение до 10 или оптимизируйте код для уменьшения количества запросов от источника.
31 |
32 | - `MIN_DICE_RATING` (число) - минимальное значение коэффициента от 0.0 до 1.0, при котором элемент считается наилучшим совпадением при импорте, например, треков в Spotify. По умолчанию _0.6005_.
33 | Если найденный элемент будет иметь меньшее значение, он отбрасывается. Когда несколько элементов удовлетворяют минимальному значению, выбирается элемент с наибольшим из них.
34 |
35 | # Идентификатор
36 |
37 | Таблицы ниже показывают как получить идентификатор из ссылки или URI.
38 |
39 | ## Плейлист {docsify-ignore}
40 |
41 | | id или playlistId | URI | Ссылка |
42 | |-|-|-|
43 | | 5ErHcGR1VdYQmsrd6vVeSV | spotify:playlist:**5ErHcGR1VdYQmsrd6vVeSV** | [open.spotify.com/playlist/**5ErHcGR1VdYQmsrd6vVeSV**?si=123](open.spotify.com/playlist/5ErHcGR1VdYQmsrd6vVeSV) |
44 | | 4vTwFTW4DytSY1N62itnwz | spotify:playlist:**4vTwFTW4DytSY1N62itnwz** | [open.spotify.com/playlist/**4vTwFTW4DytSY1N62itnwz**?si=123](open.spotify.com/playlist/4vTwFTW4DytSY1N62itnwz) |
45 |
46 | ## Пользователь {docsify-ignore}
47 |
48 | Для старых аккаунтов равен логину. Для новых аккаунтов последовательность букв и цифр.
49 |
50 | | userId | URI | Ссылка |
51 | |-|-|-|
52 | | glennpmcdonald | spotify:user:**glennpmcdonald** | [open.spotify.com/user/**glennpmcdonald**](open.spotify.com/user/glennpmcdonald) |
53 | | ldxdnznzgvvftcpw09kwqm151 | spotify:user:**ldxdnznzgvvftcpw09kwqm151** | [open.spotify.com/user/**ldxdnznzgvvftcpw09kwqm151**](open.spotify.com/user/ldxdnznzgvvftcpw09kwqm151) |
54 |
55 | # Описание параметров объектов
56 |
57 | Таблица описывает основные ключи объектов Spotify в вольном переводе. Оригинал можно прочитать [здесь](https://developer.spotify.com/documentation/web-api/reference/tracks/get-audio-features/).
58 |
59 | | Ключ | Диапазон | Описание |
60 | |-|-|-|
61 | | `popularity` | 0 - 100 |Популярность трека, исполнителя или альбома. Более популярны те, что ближе к 100.
Трек. Рассчитывается исходя из общего числа прослушиваний и насколько они недавние. Трек с большим количеством недавних прослушиваний будет более популярным, чем трек с большим количество старых прослушиваний. Значение может иметь лаг в несколько дней, то есть не обновляется в реальном времени.
Исполнитель и альбом. Рассчитывается исходя из популярности треков.
62 | | `duration_ms` | 0 - 0+ | Продолжительность трека в миллисекундах ([калькулятор](https://www.google.ru/search?ie=UTF-8&q=%D0%BC%D0%B8%D0%BD%D1%83%D1%82%D1%8B%20%D0%B2%20%D0%BC%D0%B8%D0%BB%D0%BB%D0%B8%D1%81%D0%B5%D0%BA%D1%83%D0%BD%D0%B4%D1%8B%20%D0%BA%D0%B0%D0%BB%D1%8C%D0%BA%D1%83%D0%BB%D1%8F%D1%82%D0%BE%D1%80)). Полезно для удаления треков с маленькой продолжительностью путем установки минимального значения. Или наоборот большой продолжительностью.|
63 | | `explicit` | булево | Присутствие или отсутствие ненормативной лексики. В случае функции [rangeTracks](/reference/filter?id=rangetracks) значение `false` удалит треки с ненормативной лексикой. Значение `true` или отсутствие этого ключа оставит все треки.
64 | | `added_at` | строка | Дата добавления трека в плейлист в формате строки. Пример использования в шаблоне [любимо и забыто](/template?id=Любимо-и-забыто).
65 | | `genres` и `ban_genres` | массив | Жанры исполнителя или альбома. Тесты показывают, что у альбомов список всегда пуст. В случае функции [rangeTracks](/reference/filter?id=rangetracks) будут выбраны только те треки, у которых есть хотя бы один жанр из заданного массива `genres` и нет ни одного из массива `ban_genres`.
66 | | `isRemoveUnknownGenre` | булево | При `true` удаляет исполнителей с пустым списком жанров (бывает у малоизвестных). При `false` оставляет. По умолчанию `true`. |
67 | | `release_date` | даты | Период, в котором вышел альбом рассматриваемого трека в формате даты ([формат описан здесь](/reference/filter?id=rangedateabs)). Например, между 2018 и 2020 годами: `{ min: new Date('2018'), max: new Date('2020') }`
68 |
69 | ## Особенности трека (features) {docsify-ignore}
70 | | Ключ | Диапазон | Описание |
71 | |-|-|-|
72 | | `acousticness` | 0.0 - 1.0 | Доверительный интервал, оценивающий является ли трек акустическим. Значение 1.0 показывает высокую уверенность в этом. 
73 | | `danceability` | 0.0 - 1.0 | Оценивает насколько трек подходит для танца, основываясь на его темпе, стабильности ритма, битах и общих закономерностях показателей. Менее танцевальны треки близкие к 0.0 и более к 1.0 
74 | | `energy` | 0.0 - 1.0 | Оценка интенсивности и активности трека. Как правило, энергичные треки кажутся быстрыми, громкими и шумными. Например, треки жанра дэт-метал. Расчет основывается на динамическом диапазоне, громкости, тембре, скорости нарастания и общей энтропии. Менее энергичны треки близкие к 0.0 и более к 1.0 
75 | | `instrumentalness` | 0.0 - 1.0 | Оценка наличия вокала. Например, рэп или разговорный трек явно имеет вокал. Чем ближе значение к 1.0 тем более вероятно, что трек не содержит вокала. Значение выше 0.5 понимается как инструментальный трек, но вероятность выше при приближении к единице. 
76 | | `liveness` | 0.0 - 1.0 | Оценка присутствия аудитории в записи трека или live-трек. Значения выше 0.8 отражают высокую вероятность этого. 
77 | | `loudness` | -60 до 0 | Общая громкость в децибелах. Значение громкости усредняется по всему треку. Полезно при сравнении относительность громкости треков. Как правило, диапазон от -60 до 0 дБ. 
78 | | `speechiness` | 0.0 - 1.0 | Оценка количества произнесенных слов в треке. Значение близкое к 1.0 характеризует дорожку как ток-шоу, подкаст или аудио-книгу. Треки со значением выше 0.66 вероятно полностью состоят из слов. От 0.33 до 0.66 могут содержать как речь, так и музыку. Ниже 0.33 для музыки и треков без речи. 
79 | | `valence` | 0.0 - 1.0 | Оценка позитивности трека. Высокое значение говорит о более счастливом, веселом настроении. Низкое значение характерно для треков с грустным, депрессивным настроем. 
80 | | `tempo` | 30 - 210 | Общий темп трека из расчета ударов в минуту (BPM). 
81 | | `key` | 0+ | Общий ключ трека. Значения подбираются исходя из [Pitch Class](https://en.wikipedia.org/wiki/Pitch_class). То есть 0 = C, 1 = C♯/D♭, 2 = D и так далее. Если ключ не установлен, значение -1.
82 | | `mode` | 0 или 1 | Модальность трека. Мажор = 1, минор = 0.
83 | | `time_signature` | 1+ | Общая оценка сигнатуры трека - условно обозначение для определения количества ударов в каждом такте.
84 |
85 | # Жанры для отбора рекомендаций
86 |
87 | Данный перечень нужен только для [getRecomTracks](/reference/source?id=getrecomtracks). В [rangeTracks](/reference/filter?id=rangetracks) можно использовать [такой перечень](http://everynoise.com/everynoise1d.cgi?scope=all).
88 |
89 | ```
90 | a: acoustic, afrobeat, alt-rock, alternative, ambient, anime,
91 | b: black-metal, bluegrass, blues, bossanova, brazil, breakbeat, british,
92 | c: cantopop, chicago-house, children, chill, classical, club, comedy, country,
93 | d: dance, dancehall, death-metal, deep-house, detroit-techno, disco, disney, drum-and-bass, dub, dubstep,
94 | e: edm, electro, electronic, emo,
95 | f: folk, forro, french, funk,
96 | g: garage, german, gospel, goth, grindcore, groove, grunge, guitar,
97 | h: happy, hard-rock, hardcore, hardstyle, heavy-metal, hip-hop, holidays, honky-tonk, house,
98 | i: idm, indian, indie, indie-pop, industrial, iranian,
99 | j: j-dance, j-idol, j-pop, j-rock, jazz,
100 | k: k-pop, kids,
101 | l: latin, latino,
102 | m: malay, mandopop, metal, metal-misc, metalcore, minimal-techno, movies, mpb,
103 | n: new-age, new-release,
104 | o: opera,
105 | p: pagode, party, philippines-opm, piano, pop, pop-film, post-dubstep, power-pop, progressive-house, psych-rock, punk, punk-rock,
106 | r: r-n-b, rainy-day, reggae, reggaeton, road-trip, rock, rock-n-roll, rockabilly, romance,
107 | s: sad, salsa, samba, sertanejo, show-tunes, singer-songwriter, ska, sleep, songwriter, soul, soundtracks,
108 | spanish, study, summer, swedish, synth-pop,
109 | t: tango, techno, trance, trip-hop, turkish,
110 | w: work-out, world-music
111 | ```
112 |
113 | # Категории плейлистов
114 |
115 | Чтобы получить список доступных категорий для страны, запустите следующий код. Результаты в логах.
116 | ```js
117 | let listCategory = Source.getListCategory({ limit: 50, country: 'US' });
118 | console.log(listCategory.map(c => '\n' + c.name + '\n' + c.id).join('\n'));
119 | ```
120 |
--------------------------------------------------------------------------------
/docs/img/DaysRel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/DaysRel.png
--------------------------------------------------------------------------------
/docs/img/SmarterPlaylistsExample1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/SmarterPlaylistsExample1.png
--------------------------------------------------------------------------------
/docs/img/acousticness.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/acousticness.png
--------------------------------------------------------------------------------
/docs/img/cheerio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/cheerio.png
--------------------------------------------------------------------------------
/docs/img/cmdp-fast-comment.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/cmdp-fast-comment.gif
--------------------------------------------------------------------------------
/docs/img/cmdp-fast-copy.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/cmdp-fast-copy.gif
--------------------------------------------------------------------------------
/docs/img/cmdp-font.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/cmdp-font.gif
--------------------------------------------------------------------------------
/docs/img/cmdp-move-code.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/cmdp-move-code.gif
--------------------------------------------------------------------------------
/docs/img/cmdp-rename-f2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/cmdp-rename-f2.gif
--------------------------------------------------------------------------------
/docs/img/cmdp-vertical-select-no-mouse.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/cmdp-vertical-select-no-mouse.gif
--------------------------------------------------------------------------------
/docs/img/cmdp-vertical-select.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/cmdp-vertical-select.gif
--------------------------------------------------------------------------------
/docs/img/cross-add-library.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/cross-add-library.png
--------------------------------------------------------------------------------
/docs/img/cross-project-id.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/cross-project-id.png
--------------------------------------------------------------------------------
/docs/img/danceability.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/danceability.png
--------------------------------------------------------------------------------
/docs/img/debuger.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/debuger.png
--------------------------------------------------------------------------------
/docs/img/energy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/energy.png
--------------------------------------------------------------------------------
/docs/img/example-log.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/example-log.png
--------------------------------------------------------------------------------
/docs/img/favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/img/find-genres.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/find-genres.png
--------------------------------------------------------------------------------
/docs/img/fp-debug.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/fp-debug.gif
--------------------------------------------------------------------------------
/docs/img/fp-triggers-open.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/fp-triggers-open.gif
--------------------------------------------------------------------------------
/docs/img/general-property.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/general-property.gif
--------------------------------------------------------------------------------
/docs/img/goofy-command-audiolist.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/goofy-command-audiolist.jpg
--------------------------------------------------------------------------------
/docs/img/ic_mode_edit_black_24dp.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/img/install-permission-request.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/install-permission-request.png
--------------------------------------------------------------------------------
/docs/img/install-run-setProperties.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/install-run-setProperties.png
--------------------------------------------------------------------------------
/docs/img/install-step-account.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/install-step-account.png
--------------------------------------------------------------------------------
/docs/img/install-step-callback-link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/install-step-callback-link.png
--------------------------------------------------------------------------------
/docs/img/install-step-client-id2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/install-step-client-id2.png
--------------------------------------------------------------------------------
/docs/img/install-step-copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/install-step-copy.png
--------------------------------------------------------------------------------
/docs/img/install-step-create-app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/install-step-create-app.png
--------------------------------------------------------------------------------
/docs/img/install-step-dashboard-redirect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/install-step-dashboard-redirect.png
--------------------------------------------------------------------------------
/docs/img/install-step-dashboard-term.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/install-step-dashboard-term.png
--------------------------------------------------------------------------------
/docs/img/install-step-grant-permissions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/install-step-grant-permissions.png
--------------------------------------------------------------------------------
/docs/img/install-step-grant-spotify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/install-step-grant-spotify.png
--------------------------------------------------------------------------------
/docs/img/install-step-link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/install-step-link.png
--------------------------------------------------------------------------------
/docs/img/install-step-warning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/install-step-warning.png
--------------------------------------------------------------------------------
/docs/img/install-step-webapp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/install-step-webapp.png
--------------------------------------------------------------------------------
/docs/img/instrumentalness.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/instrumentalness.png
--------------------------------------------------------------------------------
/docs/img/lastfm_account_api3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/lastfm_account_api3.png
--------------------------------------------------------------------------------
/docs/img/liveness.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/liveness.png
--------------------------------------------------------------------------------
/docs/img/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/img/loudness.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/loudness.png
--------------------------------------------------------------------------------
/docs/img/new-deploy-audiolist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/new-deploy-audiolist.png
--------------------------------------------------------------------------------
/docs/img/remote-control.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/remote-control.png
--------------------------------------------------------------------------------
/docs/img/run-func.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/run-func.gif
--------------------------------------------------------------------------------
/docs/img/sp-kf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/sp-kf.png
--------------------------------------------------------------------------------
/docs/img/sp-mc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/sp-mc.png
--------------------------------------------------------------------------------
/docs/img/speechiness.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/speechiness.png
--------------------------------------------------------------------------------
/docs/img/spotify-share.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/spotify-share.gif
--------------------------------------------------------------------------------
/docs/img/tempo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/tempo.png
--------------------------------------------------------------------------------
/docs/img/update-deploy-audiolist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/update-deploy-audiolist.png
--------------------------------------------------------------------------------
/docs/img/valence.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chimildic/goofy/c5daed5daae264c4435307fd5de2f52f5f4c2bf1/docs/img/valence.png
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Goofy
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
Загрузка...
17 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/docs/install.md:
--------------------------------------------------------------------------------
1 | # Установка
2 |
3 | Вы создаете собственную копию библиотеки. Только вы имеете доступ ко всему, что происходит в этой копии.
4 |
5 | Выполняется один раз.
6 |
7 | 1. Перейдите в [Spotify Dashboard](https://developer.spotify.com/dashboard/) и нажмите `Log in`.
8 |
9 | 2. Нажмите кнопку `create app` и заполните форму. Ссылка для редиректа: `https://chimildic.github.io/spotify/auth`
10 |
11 | 
12 |
13 | 3. Перейдите к [библиотеке в Apps Script](https://script.google.com/d/1DnC4H7yjqPV2unMZ_nmB-1bDSJT9wQUJ7Wq-ijF4Nc7Fl3qnbT0FkPSr/edit?usp=sharing). Войдите в Google аккаунт, если потребуется.
14 |
15 | 4. Выберите слева в раскрывающемся меню `Общие сведения`.
16 |
17 | 
18 |
19 | На открывшейся странице, справа `Создать копию`. Откроется копия, созданная на вашем аккаунте. Переименуйте, если нужно (нажать на имя вверху страницы).
20 |
21 | 
22 |
23 | 5. Перейдите в файл `config.gs`. Вставьте `CLIENT_ID` и `CLIENT_SECRET` вместо слов `вашеЗначение`. Значения брать в созданном приложении Spotify на шаге 2 (кнопка `Settings`).
24 |
25 | 
26 |
27 | 6. Также укажите значения для `PRIVATE_CLIENT_ID` и `PRIVATE_CLIENT_SECRET` получив их [здесь](https://script.google.com/macros/s/AKfycbwwDT25i71nYAk1aICxnrXfFVDzctcmhRMqzugjEkpqmUWjGATAbMOCL5aqvlPXOIq4/exec).
28 | Если приватные ключи недоступны, скопируйте свои обычные `CLIENT_ID` и `CLIENT_SECRET`. В этом случае недоступны рекомендации от Spotify из-за их обновленных правил.
29 | Если рекомендации важны, попросите увеличить лимит в [телеграм чате](https://t.me/forum_goofy).
30 |
31 | Сохраните изменение CtrlS или иконка дискеты на панели действий
32 |
33 | 7. Запустите в редакторе выполнение функции `setProperties`.
34 |
35 | 
36 |
37 | Увидите всплывающее сообщение с необходимость предоставить права доступа. Согласитесь на выдачу.
38 |
39 | 
40 |
41 | Выберите Google аккаунт, на котором создали копию библиотеки.
42 |
43 | 
44 |
45 | Нажмите `Дополнительные настройки`, затем `Перейти на страницу "Копия Goofy (Ver. 1)"`
46 |
47 | 
48 |
49 | Нажмите кнопку `Разрешить` внизу окна.
50 |
51 | 
52 |
53 | 8. Окно закроется. Выберите `Начать развертывание` - `Пробные развертывания`
54 |
55 | 
56 |
57 | Перейдите по ссылке из `веб-приложения`. Выдайте права доступа.
58 |
59 | Установка и настройка завершены. Переходите к [первому плейлисту](/first-playlist).
--------------------------------------------------------------------------------
/docs/migrate2.md:
--------------------------------------------------------------------------------
1 | # Миграция
2 |
3 | ## Версия 2.0
4 |
5 | Новая политика Spotify API ограничела доступ к важным функциям. Подробнее [здесь](https://developer.spotify.com/blog/2024-11-27-changes-to-the-web-api).
6 | Чтобы вернуть доступ в goofy 2.0 запросы делятся между двумя приложениями: вашим и приватным. На данный момент приватное приложение продолжает отвечать на запросы к закрытой части API.
7 |
8 | 1. Зайдите в [Spotify Dashboard](https://developer.spotify.com/dashboard)
9 | 2. Выберите приложение для goofy
10 | 3. Нажмите кнопку `Settings`
11 | 4. Посмотрите статус в поле `App Status` (понадобится дальше)
12 | 5. Спуститесь ниже и добавьте в `Redirect URLs` новую ссылку: `https://chimildic.github.io/spotify/auth`
13 | 6. Откройте проект в Apps Script
14 | 7. Зайдите в файл `config` и добавьте две новые строчки в функцию `setProperties`. Пример как должно выглядеть [здесь](https://github.com/Chimildic/goofy/blob/main/config.js).
15 | ```js
16 | UserProperties.setProperty('PRIVATE_CLIENT_ID', 'вашеЗначение');
17 | UserProperties.setProperty('PRIVATE_CLIENT_SECRET', 'вашеЗначение');
18 | ```
19 |
20 | - Если ваш статус `Development mode`, перейдите по [ссылке](https://script.google.com/macros/s/AKfycbwwDT25i71nYAk1aICxnrXfFVDzctcmhRMqzugjEkpqmUWjGATAbMOCL5aqvlPXOIq4/exec), чтобы получить значения для приватного приложения.
21 |
22 | - Если ваш статус `Granted quota extension`, продублируйте свои же значения из строк `CLIENT_ID` и `CLIENT_SECRET`. Также вы можете помочь другим получить доступ к закрытому API. Сообщите о своем статусе в [телеграм чате](https://t.me/forum_goofy).
23 |
24 | 8. Скопируйте в файл `config` следующую функцию и запустите её.
25 |
26 | ```js
27 | function reset() {
28 | Admin.reset()
29 | setProperties()
30 | }
31 | ```
32 |
33 | 9. [Обновите](https://chimildic.github.io/goofy/#/tuning?id=Обновить-библиотеку) код основной библиотеки как раньше (по умолчанию файл `library`)
34 | 10. Обновите права доступа: `начать развертывание` > `пробные развертывания` > перейти по ссылке `веб-приложение` и следовать появившейся инструкции
35 |
--------------------------------------------------------------------------------
/docs/overview.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Конструктор плейлистов Spotify. Сбор треков, фильтр, обновление по событиям. Бесплатно.
11 |
12 | ## Возможности
13 |
14 | - отслеживает историю прослушиваний
15 | - собирает рекомендации и новые релизы
16 | - имеет большой набор фильтров
17 | - импортирует FM-радио и Last.fm
18 | - поддерживает расширенный поиск
19 | - позволяет динамически менять очередь треков
20 | - работает по расписанию и событиям Tasker
21 |
--------------------------------------------------------------------------------
/docs/reference/_sidebar.md:
--------------------------------------------------------------------------------
1 | - [На главную](/)
2 | - [Список модулей](/reference/index.md)
3 | - [Cache](/reference/cache.md)
4 | - [Clerk](/reference/clerk.md)
5 | - [Combiner](/reference/combiner.md)
6 | - [Filter](/reference/filter.md)
7 | - [Lastfm](/reference/lastfm.md)
8 | - [Library](/reference/library.md)
9 | - [Order](/reference/order.md)
10 | - [Player](/reference/player.md)
11 | - [Playlist](/reference/playlist.md)
12 | - [RecentTracks](/reference/recenttracks.md)
13 | - [Search](/reference/search.md)
14 | - [Selector](/reference/selector.md)
15 | - [Source](/reference/source.md)
16 | - [Вспомогательное описание](/reference/desc.md)
17 | -
--------------------------------------------------------------------------------
/docs/reference/cache.md:
--------------------------------------------------------------------------------
1 | # Cache {docsify-ignore}
2 |
3 | Методы управления данными Google Диска.
4 |
5 | По умолчанию, без указания расширения файла, подразумевается `json`. При явном указании поддерживается текстовый формат `txt`.
6 |
7 | | Метод | Тип результата | Краткое описание |
8 | |-------|----------------|------------------|
9 | | [append](/reference/cache?id=append) | Число | Присоединить данные к массиву из файла. |
10 | | [compressArtists](/reference/cache?id=compressartists) | - | Удалить незначимые данные о исполнителях. |
11 | | [compressTracks](/reference/cache?id=compresstracks) | - | Удалить незначимые данные о треках. |
12 | | [copy](/reference/cache?id=copy) | Строка | Создать копию файла в папке исходного файла. |
13 | | [read](/reference/cache?id=read) | Массив/Объект/Строка | Прочитать данные из файла. |
14 | | [remove](/reference/cache?id=remove) | - | Переместить файл в корзину Google Диска. |
15 | | [rename](/reference/cache?id=rename) | - | Переименовать файл. |
16 | | [write](/reference/cache?id=write) | - | Записать данные в файл. |
17 |
18 | ## append
19 |
20 | Присоединить данные к массиву из файла. Создает файл, если его не существует.
21 |
22 | ### Аргументы :id=append-arguments {docsify-ignore}
23 |
24 | | Имя | Тип | Описание |
25 | |-----|-----|----------|
26 | | `filepath` | Строка | [Путь до файла](/best-practices?id=Путь-до-файла). |
27 | | `content` | Массив | Данные для добавления. |
28 | | `place` | Строка | Место соединения: `begin` - начало, `end` - конец. По умолчанию `end`. |
29 | | `limit` | Число | Ограничить число элементов массива после присоединения новых данных. По умолчанию 200 тысяч элементов. |
30 |
31 | ### Возврат :id=append-return {docsify-ignore}
32 |
33 | `contentLength` (число) - количество элементов после добавления.
34 |
35 | ### Примеры :id=append-examples {docsify-ignore}
36 |
37 | 1. Присоединить треки плейлиста в начало файла. Ограничить массив 5 тысячами треков после присоединения.
38 |
39 | ```js
40 | let tracks = Source.getPlaylistTracks('playlist name', 'id');
41 | Cache.append('filename.json', tracks, 'begin', 5000);
42 | ```
43 |
44 | 2. Присоединить треки плейлиста в конец файла.
45 |
46 | ```js
47 | let tracks = Source.getPlaylistTracks('playlist name', 'id');
48 | Cache.append('filename.json', tracks);
49 | ```
50 |
51 | ## compressArtists
52 |
53 | Удалить незначимые данные о исполнителях. Использовать до сохранения в файл для уменьшения его размера.
54 |
55 | ### Аргументы :id=compressartists-arguments {docsify-ignore}
56 |
57 | | Имя | Тип | Описание |
58 | |-----|-----|----------|
59 | | `artists` | Массив | Исполнители, к которых требуется удалить незначимые данные. |
60 |
61 | ### Возврат :id=compressartists-return {docsify-ignore}
62 |
63 | Нет возвращаемого значения. Изменяет входной массив.
64 |
65 | ### Примеры :id=compressartists-examples {docsify-ignore}
66 |
67 | 1. Уменьшить размер файла с массивом исполнителей.
68 |
69 | ```js
70 | let filename = 'artists.json';
71 | let artists = Cache.read(filename);
72 | Cache.compressArtists(artists);
73 | Cache.write(filename, artists);
74 | ```
75 |
76 | ## compressTracks
77 |
78 | Удалить незначимые данные о треках. Использовать до сохранения в файл для уменьшения его размера. Включает вызов [compressArtists](/reference/cache?id=compressartists)
79 |
80 | ### Аргументы :id=compresstracks-arguments {docsify-ignore}
81 |
82 | | Имя | Тип | Описание |
83 | |-----|-----|----------|
84 | | `tracks` | Массив | Треки, в которых требуется удалить незначимые данные. |
85 |
86 | ### Возврат :id=compresstracks-return {docsify-ignore}
87 |
88 | Нет возвращаемого значения. Изменяет входной массив.
89 |
90 | ### Примеры :id=compresstracks-examples {docsify-ignore}
91 |
92 | 1. Уменьшить размер файла с массивом треков.
93 |
94 | ```js
95 | let filename = 'tracks.json';
96 | let tracks = Cache.read(filename);
97 | Cache.compressTracks(tracks);
98 | Cache.write(filename, tracks);
99 | ```
100 |
101 | ## copy
102 |
103 | Создать копию файла в папке исходного файла.
104 |
105 | ### Аргументы :id=copy-arguments {docsify-ignore}
106 |
107 | | Имя | Тип | Описание |
108 | |-----|-----|----------|
109 | | `filepath` | Строка | [Путь до файла](/best-practices?id=Путь-до-файла). |
110 |
111 | ### Возврат :id=copy-return {docsify-ignore}
112 |
113 | `filecopypath` (строка) - путь до созданной копии.
114 |
115 | ### Примеры :id=copy-examples {docsify-ignore}
116 |
117 | 1. Создать копию файла и прочитать ее данные.
118 |
119 | ```js
120 | let filename = 'tracks.json';
121 | let filecopyname = Cache.copy(filename);
122 | let tracks = Cache.read(filecopyname);
123 | ```
124 |
125 | ## read
126 |
127 | Прочитать данные из файла.
128 |
129 | ### Аргументы :id=read-arguments {docsify-ignore}
130 |
131 | | Имя | Тип | Описание |
132 | |-----|-----|----------|
133 | | `filepath` | Строка | [Путь до файла](/best-practices?id=Путь-до-файла). |
134 |
135 | ### Возврат :id=read-return {docsify-ignore}
136 |
137 | `content` (массив/объект/строка) - данные из файла.
138 |
139 | Если файла не существует, проверяется расширение в строке `filepath`. При отсутствии или равенстве _json_ - вернет пустой массив. В остальных случаях - пустую строку.
140 |
141 | ### Примеры :id=read-examples {docsify-ignore}
142 |
143 | 1. Прочитать данные из файла и добавить в плейлист.
144 |
145 | ```js
146 | let tracks = Cache.read('tracks.json');
147 | Playlist.saveAsNew({
148 | name: 'Треки из файла',
149 | tracks: tracks,
150 | });
151 | ```
152 |
153 | ## remove
154 |
155 | Переместить файл в корзину Google Диска. Данные из корзины удаляются через 30 дней.
156 |
157 | ### Аргументы :id=remove-arguments {docsify-ignore}
158 |
159 | | Имя | Тип | Описание |
160 | |-----|-----|----------|
161 | | `filepath` | Строка | [Путь до файла](/best-practices?id=Путь-до-файла). |
162 |
163 | ### Возврат :id=remove-return {docsify-ignore}
164 |
165 | Нет возвращаемого значения.
166 |
167 | ### Примеры :id=remove-examples {docsify-ignore}
168 |
169 | 1. Переместить файл в корзину
170 |
171 | ```js
172 | Cache.remove('filepath.json');
173 | ```
174 |
175 | ## rename
176 |
177 | Переименовать файл.
178 |
179 | !> Не используйте имена `SpotifyRecentTracks`, `LastfmRecentTracks`, `BothRecentTracks`. Они нужны в механизме накопления [истории прослушиваний](/details?id=История-прослушиваний).
180 |
181 | ### Аргументы :id=rename-arguments {docsify-ignore}
182 |
183 | | Имя | Тип | Описание |
184 | |-----|-----|----------|
185 | | `filepath` | Строка | [Путь до файла](/best-practices?id=Путь-до-файла). |
186 | | `newFilename` | Строка | Новое имя файла (не путь) |
187 |
188 | ### Возврат :id=rename-return {docsify-ignore}
189 |
190 | Нет возвращаемого значения.
191 |
192 | ### Примеры :id=rename-examples {docsify-ignore}
193 |
194 | 1. Переименовать файл.
195 |
196 | ```js
197 | Cache.rename('filename.json', 'newname.json');
198 | ```
199 |
200 | ## write
201 |
202 | Записать данные в файл. Создает файл, если его не существует. Перезаписывает файл, если он существует.
203 |
204 | ### Аргументы :id=write-arguments {docsify-ignore}
205 |
206 | | Имя | Тип | Описание |
207 | |-----|-----|----------|
208 | | `filepath` | Строка | [Путь до файла](/best-practices?id=Путь-до-файла). |
209 | | `content` | Массив | Данные для записи. |
210 |
211 | ### Возврат :id=write-return {docsify-ignore}
212 |
213 | Нет возвращаемого значения.
214 |
215 | ### Примеры :id=write-examples {docsify-ignore}
216 |
217 | 1. Записать любимые треки в файл.
218 |
219 | ```js
220 | let tracks = Sourct.getSavedTracks();
221 | Cache.write('liked.json', tracks);
222 | ```
223 |
--------------------------------------------------------------------------------
/docs/reference/clerk.md:
--------------------------------------------------------------------------------
1 | # Clerk {docsify-ignore}
2 |
3 | Методы выполнения нескольких функций в разное время за один триггер. Подробнее в [полезном опыте](/best-practices?id=Продвинутый-триггер)
4 |
5 | | Метод | Тип результата | Краткое описание |
6 | |-------|----------------|------------------|
7 | | [runOnceAfter](/reference/clerk?id=runonceafter) | Булево | Выполнять задачу каждый день после заданного времени. |
8 | | [runOnceAWeek](/reference/clerk?id=runonceaweek) | Булево | Выполнять задачу в определенный день недели. |
9 |
10 | ## runOnceAfter
11 |
12 | Выполнять задачу каждый день после заданного времени.
13 |
14 | ### Аргументы :id=runonceafter-arguments {docsify-ignore}
15 |
16 | | Имя | Тип | Описание |
17 | |-----|-----|----------|
18 | | `timeStr` | Строка | Время, после которого задача запускается один раз. |
19 | | `callback` | Функция | Выполняемая функция. Имя функции обязано быть уникальным среди всех функций, вызываемых _Clerk_. |
20 |
21 | ### Возврат :id=runonceafter-return {docsify-ignore}
22 |
23 | `isRun` (булево) - при `true` функция выполнялась, при `false` функция не запускалась.
24 |
25 | ### Примеры :id=runonceafter-examples {docsify-ignore}
26 |
27 | 1. Выполнить три функции в разное время за один триггер
28 |
29 | ```js
30 | // Триггер каждый час
31 | function updatePlaylists() {
32 | updateEveryHour() // каждый час
33 | Clerk.runOnceAfter('15:00', updateInDay) // раз в день
34 | Clerk.runOnceAfter('21:00', updateInEvening) // раз в день
35 |
36 | function updateEveryHour() {
37 | // ...
38 | }
39 |
40 | function updateInDay() {
41 | // ...
42 | }
43 |
44 | function updateInEvening() {
45 | // ...
46 | }
47 | }
48 | ```
49 |
50 | ## runOnceAWeek
51 |
52 | Выполнять задачу в определенный день недели.
53 |
54 | ### Аргументы :id=runonceaweek-arguments {docsify-ignore}
55 |
56 | | Имя | Тип | Описание |
57 | |-----|-----|----------|
58 | | `dayStr` | Строка | День недели на английском. |
59 | | `timeStr` | Строка | Время, после которого задача запускается один раз. |
60 | | `callback` | Функция | Выполняемая функция. Имя функции обязано быть уникальным среди всех функций, вызываемых _Clerk_. |
61 |
62 | ### Возврат :id=runonceaweek-return {docsify-ignore}
63 |
64 | `isRun` (булево) - при `true` функция выполнялась, при `false` функция не запускалась.
65 |
66 | ### Примеры :id=runonceaweek-examples {docsify-ignore}
67 |
68 | 1. Выполнить три функции в разное время за один триггер
69 |
70 | ```js
71 | // Триггер каждые 15 минут
72 | function updatePlaylists() {
73 | update15() // каждые 15 минут
74 | Clerk.runOnceAWeek('monday', '12:00', updateMonday) // каждый понедельник после 12
75 | Clerk.runOnceAWeek('saturday', '16:00', updateSaturday) // каждую субботу после 16
76 |
77 | function update15() {
78 | // ...
79 | }
80 |
81 | function updateMonday() {
82 | // ...
83 | }
84 |
85 | function updateSaturday() {
86 | // ...
87 | }
88 | }
89 | ```
--------------------------------------------------------------------------------
/docs/reference/combiner.md:
--------------------------------------------------------------------------------
1 | # Combiner {docsify-ignore}
2 |
3 | Методы комбинирования элементов.
4 |
5 | | Метод | Тип результата | Краткое описание |
6 | |-------|----------------|------------------|
7 | | [alternate](/reference/combiner?id=alternate) | Массив | Чередовать элементы массивов. Один шаг - один элемент массива. |
8 | | [mixin](/reference/combiner?id=mixin) | Массив | Чередовать элементы двух массивов. Один шаг - один и больше элементов от одного массива. |
9 | | [mixinMulti](/reference/combiner?id=mixinmulti) | Массив | Чередовать элементы неограниченного количества массивов. Один шаг - один и больше элементов от одного массива. |
10 | | [push](/reference/combiner?id=push) | Массив | Добавить в конец первого массива элементы второго массива и так далее. |
11 |
12 | ## alternate
13 |
14 | Чередовать элементы массивов. Один шаг - один элемент массива.
15 |
16 | ### Аргументы :id=alternate-arguments {docsify-ignore}
17 |
18 | | Имя | Тип | Описание |
19 | |-----|-----|----------|
20 | | `bound` | Строка | Граница чередования. При `min` чередование заканчивается, когда один из источников закончится. При `max` чередование продолжается до тех пор, пока есть незатронутые элементы. |
21 | | ``...arrays`` | Массивы | Источники элементов для чередования. |
22 |
23 | ### Возврат :id=alternate-return {docsify-ignore}
24 |
25 | `resultArray` (массив) - новый массив, в котором чередуются элементы источников.
26 |
27 | ### Примеры :id=alternate-examples {docsify-ignore}
28 |
29 | 1. Чередовать элементы трех массивов.
30 |
31 | ```js
32 | let firstArray = [1, 3, 5];
33 | let secondeArray = [2, 4, 6, 8, 10];
34 | let thirdArray = [100, 200, 300];
35 | let resultArray = Combiner.alternate('max', firstArray, secondeArray, thirdArray);
36 | // результат 1, 2, 100, 3, 4, 200, 5, 6, 300, 8, 10
37 | ```
38 |
39 | 2. Чередовать топ прослушиваний за месяц и любимые треки.
40 |
41 | ```js
42 | let topTracks = Source.getTopTracks('short'); // допустим, 50 треков
43 | let savedTracks = Source.getSavedTracks(20); //допустим, 20 треков
44 | let resultArray = Combiner.alternate('min', topTracks, savedTracks);
45 | // результат содержит 40 треков
46 | ```
47 |
48 | ## mixin
49 |
50 | Чередовать элементы двух массивов. Один шаг - один и больше элементов от одного массива. Включает вызов [mixinMulti](/reference/combiner?id=mixinmulti).
51 |
52 | ### Аргументы :id=mixin-arguments {docsify-ignore}
53 |
54 | | Имя | Тип | Описание |
55 | |-----|-----|----------|
56 | | `xArray` | Массив | Первый источник. |
57 | | `yArray` | Массив | Второй источник. |
58 | | `xRow` | Число | Количество элементов подряд от первого источника. |
59 | | `yRow` | Число | Количество элементов подряд от второго источника. |
60 | | `toLimitOn` | Булево | Элементы чередуются до тех пор, пока пропорцию можно сохранить. Если `true` лишние элементы не включаются в результат. Если `false` добавляются в конец результата. По умолчанию `false`. |
61 |
62 | ### Возврат :id=mixin-return {docsify-ignore}
63 |
64 | `resultArray` (массив) - новый массив, в котором чередуются элементы двух источников в заданной пропорции.
65 |
66 | ### Примеры :id=mixin-examples {docsify-ignore}
67 |
68 | 1. Чередовать треки плейлистов и любимые треки в соотношении 5 к 1. Отбросить лишнее.
69 |
70 | ```js
71 | let tracks = Source.getTracks(playlistArray);
72 | let savedTracks = Source.getSavedTracks();
73 | let resultArray = Combiner.mixin(tracks, savedTracks, 5, 1, true);
74 | ```
75 |
76 | ## mixinMulti
77 |
78 | Чередовать элементы неограниченного количества массивов. Один шаг - один и больше элементов от одного массива.
79 |
80 | ### Аргументы :id=mixinmulti-arguments {docsify-ignore}
81 |
82 | | Имя | Тип | Описание |
83 | |-----|-----|----------|
84 | | `params` | Объект | Параметры чередования. |
85 |
86 | #### Параметры чередования :id=mixinmulti-params {docsify-ignore}
87 |
88 | | Имя | Тип | Описание |
89 | |-----|-----|----------|
90 | | `source` | Массив | Массивы источников. |
91 | | `inRow` | Массив | Элементы, которые устанавливают количество подряд идущих элементов для каждого источника. |
92 | | `toLimitOn` | Булево | Элементы чередуются до тех пор, пока пропорцию можно сохранить. Если `true` лишние элементы не включаются в результат. Если `false` добавляются в конец результата. По умолчанию `false`. При `toLimitOn = true`, первая итерация проверяет количество элементов. Если элементов меньше, чем задано соотношением, вернется пустой массив. |
93 |
94 | ### Возврат :id=mixinmulti-return {docsify-ignore}
95 |
96 | `resultArray` (массив) - новый массив, в котором чередуются элементы источников в заданной пропорции.
97 |
98 | ### Примеры :id=mixinmulti-examples {docsify-ignore}
99 |
100 | 1. Чередовать элементы в соотношении 1:1:1. Сохранить все элементы.
101 |
102 | ```js
103 | let x = [1, 2, 3, 4, 5];
104 | let y = [10, 20, 30, 40];
105 | let z = [100, 200, 300];
106 | let result = Combiner.mixinMulti({
107 | source: [x, y, z],
108 | inRow: [1, 1, 1],
109 | });
110 | // 1, 10, 100, 2, 20, 200, 3, 30, 300, 4, 40, 5
111 | ```
112 |
113 | 2. Чередовать элементы в соотношении 2:4:2 до тех пор, пока можно сохранить последовательность.
114 |
115 | ```js
116 | let x = [1, 2, 3, 4, 5];
117 | let y = [10, 20, 30, 40];
118 | let z = [100, 200, 300];
119 | let result = Combiner.mixinMulti({
120 | toLimitOn: true,
121 | source: [x, y, z],
122 | inRow: [2, 4, 2],
123 | });
124 | // 1, 2, 10, 20, 30, 40, 100, 200
125 | ```
126 |
127 | 3. Чередовать рекомендации, любимые треки и историю прослушиваний в соотношении 4:1:1 до тех пор, пока можно сохранить последовательность.
128 |
129 | ```js
130 | let recom = Source.getRecomTracks();
131 | let saved = Source.getSavedTracks();
132 | let recent = RecentTracks.get();
133 | let tracks = Combiner.mixinMulti({
134 | toLimitOn: true,
135 | source: [recom, saved, recent],
136 | inRow: [4, 1, 1],
137 | });
138 | ```
139 |
140 | ## push
141 |
142 | Добавить в конец первого массива элементы второго массива и так далее.
143 |
144 | ### Аргументы :id=push-arguments {docsify-ignore}
145 |
146 | | Имя | Тип | Описание |
147 | |-----|-----|----------|
148 | | `sourceArray` | Массив | Первый массив. К нему добавляются элементы последующих массивов. |
149 | | `...additionalArray` | Массивы | Последующие массивы для добавления к предыдущему. |
150 |
151 | ### Возврат :id=push-return {docsify-ignore}
152 |
153 | `sourceArray` (массив) - оригинальный первый массив после добавления элементов из других массивов.
154 |
155 | ### Примеры :id=push-examples {docsify-ignore}
156 |
157 | 1. Добавить элементы второго массива в конец первого массива.
158 |
159 | ```js
160 | let firstArray = Source.getTracks(playlistArray); // допустим, 20 треков
161 | let secondeArray = Source.getSavedTracks(); // допустим, 40 треков
162 | Combiner.push(firstArray, secondeArray);
163 | // теперь в firstArray 60 треков
164 | ```
165 |
166 | 2. Добавить к первому массиву элементы двух других.
167 |
168 | ```js
169 | let firstArray = Source.getTracks(playlistArray); // допустим, 25 треков
170 | let secondeArray = Source.getSavedTracks(); // допустим, 100 треков
171 | let thirdArray = Source.getPlaylistTracks(); // допустим, 20 треков
172 | Combiner.push(firstArray, secondeArray, thirdArray);
173 | // теперь в firstArray 145 треков
174 | ```
175 |
--------------------------------------------------------------------------------
/docs/reference/desc.md:
--------------------------------------------------------------------------------
1 | # Вспомогательное описание
2 |
3 | ## Идентификатор
4 |
5 | Таблицы ниже показывают как получить идентификатор из ссылки или URI.
6 |
7 | ### Плейлист {docsify-ignore}
8 |
9 | | id или playlistId | URI | Ссылка |
10 | |-|-|-|
11 | | 5ErHcGR1VdYQmsrd6vVeSV | spotify:playlist:**5ErHcGR1VdYQmsrd6vVeSV** | [open.spotify.com/playlist/**5ErHcGR1VdYQmsrd6vVeSV**?si=123](open.spotify.com/playlist/5ErHcGR1VdYQmsrd6vVeSV) |
12 | | 4vTwFTW4DytSY1N62itnwz | spotify:playlist:**4vTwFTW4DytSY1N62itnwz** | [open.spotify.com/playlist/**4vTwFTW4DytSY1N62itnwz**?si=123](open.spotify.com/playlist/4vTwFTW4DytSY1N62itnwz) |
13 |
14 | ### Пользователь {docsify-ignore}
15 |
16 | Для старых аккаунтов равен логину. Для новых аккаунтов последовательность букв и цифр.
17 |
18 | | userId | URI | Ссылка |
19 | |-|-|-|
20 | | glennpmcdonald | spotify:user:**glennpmcdonald** | [open.spotify.com/user/**glennpmcdonald**](open.spotify.com/user/glennpmcdonald) |
21 | | ldxdnznzgvvftcpw09kwqm151 | spotify:user:**ldxdnznzgvvftcpw09kwqm151** | [open.spotify.com/user/**ldxdnznzgvvftcpw09kwqm151**](open.spotify.com/user/ldxdnznzgvvftcpw09kwqm151) |
22 |
23 | ## Описание параметров объектов
24 |
25 | Таблица описывает основные ключи объектов Spotify в вольном переводе. Оригинал можно прочитать [здесь](https://developer.spotify.com/documentation/web-api/reference/tracks/get-audio-features/).
26 |
27 | | Ключ | Диапазон | Описание |
28 | |-|-|-|
29 | | `popularity` | 0 - 100 |Популярность трека, исполнителя или альбома. Более популярны те, что ближе к 100.
Трек. Рассчитывается исходя из общего числа прослушиваний и насколько они недавние. Трек с большим количеством недавних прослушиваний будет более популярным, чем трек с большим количество старых прослушиваний. Значение может иметь лаг в несколько дней, то есть не обновляется в реальном времени.
Исполнитель и альбом. Рассчитывается исходя из популярности треков.
30 | | `duration_ms` | 0 - 0+ | Продолжительность трека в миллисекундах ([калькулятор](https://www.google.ru/search?ie=UTF-8&q=%D0%BC%D0%B8%D0%BD%D1%83%D1%82%D1%8B%20%D0%B2%20%D0%BC%D0%B8%D0%BB%D0%BB%D0%B8%D1%81%D0%B5%D0%BA%D1%83%D0%BD%D0%B4%D1%8B%20%D0%BA%D0%B0%D0%BB%D1%8C%D0%BA%D1%83%D0%BB%D1%8F%D1%82%D0%BE%D1%80)). Полезно для удаления треков с маленькой продолжительностью путем установки минимального значения. Или наоборот большой продолжительностью.|
31 | | `explicit` | булево | Присутствие или отсутствие ненормативной лексики. В случае функции [rangeTracks](/reference/filter?id=rangetracks) значение `false` удалит треки с ненормативной лексикой. Значение `true` или отсутствие этого ключа оставит все треки.
32 | | `added_at` | строка | Дата добавления трека в плейлист в формате строки. Пример использования в шаблоне [любимо и забыто](/template?id=Любимо-и-забыто).
33 | | `genres` и `ban_genres` | массив | Жанры исполнителя или альбома. Тесты показывают, что у альбомов список всегда пуст. В случае функции [rangeTracks](/reference/filter?id=rangetracks) будут выбраны только те треки, у которых есть хотя бы один жанр из заданного массива `genres` и нет ни одного из массива `ban_genres`.
34 | | `release_date` | даты | Период, в котором вышел альбом рассматриваемого трека в формате даты ([формат описан здесь](/reference/filter?id=rangedateabs)). Например, между 2018 и 2020 годами: `{ min: new Date('2018'), max: new Date('2020') }`
35 |
36 | ## Особенности трека (features) {docsify-ignore}
37 | | Ключ | Диапазон | Описание |
38 | |-|-|-|
39 | | `acousticness` | 0.0 - 1.0 | Доверительный интервал, оценивающий является ли трек акустическим. Значение 1.0 показывает высокую уверенность в этом. 
40 | | `danceability` | 0.0 - 1.0 | Оценивает насколько трек подходит для танца, основываясь на его темпе, стабильности ритма, битах и общих закономерностях показателей. Менее танцевальны треки близкие к 0.0 и более к 1.0 
41 | | `energy` | 0.0 - 1.0 | Оценка интенсивности и активности трека. Как правило, энергичные треки кажутся быстрыми, громкими и шумными. Например, треки жанра дэт-метал. Расчет основывается на динамическом диапазоне, громкости, тембре, скорости нарастания и общей энтропии. Менее энергичны треки близкие к 0.0 и более к 1.0 
42 | | `instrumentalness` | 0.0 - 1.0 | Оценка наличия вокала. Например, рэп или разговорный трек явно имеет вокал. Чем ближе значение к 1.0 тем более вероятно, что трек не содержит вокала. Значение выше 0.5 понимается как инструментальный трек, но вероятность выше при приближении к единице. 
43 | | `liveness` | 0.0 - 1.0 | Оценка присутствия аудитории в записи трека или live-трек. Значения выше 0.8 отражают высокую вероятность этого. 
44 | | `loudness` | -60 до 0 | Общая громкость в децибелах. Значение громкости усредняется по всему треку. Полезно при сравнении относительность громкости треков. Как правило, диапазон от -60 до 0 дБ. 
45 | | `speechiness` | 0.0 - 1.0 | Оценка количества произнесенных слов в треке. Значение близкое к 1.0 характеризует дорожку как ток-шоу, подкаст или аудио-книгу. Треки со значением выше 0.66 вероятно полностью состоят из слов. От 0.33 до 0.66 могут содержать как речь, так и музыку. Ниже 0.33 для музыки и треков без речи. 
46 | | `valence` | 0.0 - 1.0 | Оценка позитивности трека. Высокое значение говорит о более счастливом, веселом настроении. Низкое значение характерно для треков с грустным, депрессивным настроем. 
47 | | `tempo` | 30 - 210 | Общий темп трека из расчета ударов в минуту (BPM). 
48 | | `key` | 0+ | Общий ключ трека. Значения подбираются исходя из [Pitch Class](https://en.wikipedia.org/wiki/Pitch_class). То есть 0 = C, 1 = C♯/D♭, 2 = D и так далее. Если ключ не установлен, значение -1.
49 | | `mode` | 0 или 1 | Модальность трека. Мажор = 1, минор = 0.
50 | | `time_signature` | 1+ | Общая оценка сигнатуры трека - условно обозначение для определения количества ударов в каждом такте.
51 |
52 | ## Жанры для отбора рекомендаций
53 |
54 | Данный перечень нужен только для [getRecomTracks](/reference/source?id=getrecomtracks). В [rangeTracks](/reference/filter?id=rangetracks) можно использовать [такой перечень](http://everynoise.com/everynoise1d.cgi?scope=all).
55 |
56 | ```
57 | a: acoustic, afrobeat, alt-rock, alternative, ambient, anime,
58 | b: black-metal, bluegrass, blues, bossanova, brazil, breakbeat, british,
59 | c: cantopop, chicago-house, children, chill, classical, club, comedy, country,
60 | d: dance, dancehall, death-metal, deep-house, detroit-techno, disco, disney, drum-and-bass, dub, dubstep,
61 | e: edm, electro, electronic, emo,
62 | f: folk, forro, french, funk,
63 | g: garage, german, gospel, goth, grindcore, groove, grunge, guitar,
64 | h: happy, hard-rock, hardcore, hardstyle, heavy-metal, hip-hop, holidays, honky-tonk, house,
65 | i: idm, indian, indie, indie-pop, industrial, iranian,
66 | j: j-dance, j-idol, j-pop, j-rock, jazz,
67 | k: k-pop, kids,
68 | l: latin, latino,
69 | m: malay, mandopop, metal, metal-misc, metalcore, minimal-techno, movies, mpb,
70 | n: new-age, new-release,
71 | o: opera,
72 | p: pagode, party, philippines-opm, piano, pop, pop-film, post-dubstep, power-pop, progressive-house, psych-rock, punk, punk-rock,
73 | r: r-n-b, rainy-day, reggae, reggaeton, road-trip, rock, rock-n-roll, rockabilly, romance,
74 | s: sad, salsa, samba, sertanejo, show-tunes, singer-songwriter, ska, sleep, songwriter, soul, soundtracks,
75 | spanish, study, summer, swedish, synth-pop,
76 | t: tango, techno, trance, trip-hop, turkish,
77 | w: work-out, world-music
78 | ```
79 |
80 | ## Категории плейлистов
81 |
82 | Чтобы получить список доступных категорий для страны, запустите следующий код. Результаты в логах.
83 | ```js
84 | let listCategory = Source.getListCategory({ limit: 50, country: 'US' });
85 | console.log(listCategory.map(c => '\n' + c.name + '\n' + c.id).join('\n'));
86 | ```
87 |
--------------------------------------------------------------------------------
/docs/reference/index.md:
--------------------------------------------------------------------------------
1 | # Модули
2 |
3 | | Модуль | Краткое описание |
4 | |--------|------------------|
5 | | [Cache](/reference/cache.md) | Методы управления данными Google Диска. |
6 | | [Clerk](/reference/clerk.md) | Методы выполнения нескольких функций в разное время за один триггер. |
7 | | [Combiner](/reference/combiner.md) | Методы комбинирования элементов. |
8 | | [Filter](/reference/filter.md) | Методы отсеивания элементов. |
9 | | [Lastfm](/reference/lastfm.md) | Методы взаимодействия с Last.fm. |
10 | | [Library](/reference/library.md) | Методы по управлению лайками и подписками. |
11 | | [Order](/reference/order.md) | Методы сортировки. |
12 | | [Player](/reference/player.md) | Методы управления плеером. |
13 | | [Playlist](/reference/playlist.md) | Методы по созданию и управлению плейлистами. |
14 | | [RecentTracks](/reference/recenttracks.md) | Методы работы с историей прослушиваний. |
15 | | [Search](/reference/search.md) | Методы поиска. |
16 | | [Selector](/reference/selector.md) | Методы отбора и ветвления. |
17 | | [Source](/reference/source.md) | Методы получения элементов Spotify. |
18 |
--------------------------------------------------------------------------------
/docs/reference/library.md:
--------------------------------------------------------------------------------
1 | # Library
2 |
3 | Методы по управлению лайками и подписками.
4 |
5 | | Метод | Тип результата | Краткое описание |
6 | |-------|----------------|------------------|
7 | | [checkFavoriteTracks](/reference/library?id=checkfavoritetracks) | - | Проверить каждый трек на наличие в любимых (лайках). |
8 | | [deleteAlbums](/reference/library?id=deletealbums) | - | Удалить альбомы из библиотеки (лайки на альбомах). |
9 | | [deleteFavoriteTracks](/reference/library?id=deletefavoritetracks) | - | Удалить треки из любимых (убрать лайки). |
10 | | [followArtists](/reference/library?id=followartists) | - | Подписаться на исполнителей. |
11 | | [followPlaylists](/reference/library?id=followplaylists) | - | Подписаться на плейлисты. |
12 | | [saveAlbums](/reference/library?id=savealbums) | - | Сохранить альбомы в библиотеку. |
13 | | [saveFavoriteTracks](/reference/library?id=savefavoritetracks) | - | Добавить треки в любимые (поставить лайк). |
14 | | [unfollowArtists](/reference/library?id=unfollowartists) | - | Отписаться от исполнителей. |
15 | | [unfollowPlaylists](/reference/library?id=unfollowplaylists) | - | Отписаться от плейлистов. |
16 |
17 | ## checkFavoriteTracks
18 |
19 | Проверить каждый трек на наличие в любимых (лайках).
20 |
21 | ### Аргументы :id=checkfavoritetracks-arguments {docsify-ignore}
22 |
23 | | Имя | Тип | Описание |
24 | |-----|-----|----------|
25 | | `tracks` | Массив | Проверяемы треки. Значимо только _id_. |
26 |
27 | ### Возврат :id=checkfavoritetracks-return {docsify-ignore}
28 |
29 | Нет возвращаемого значения.
30 |
31 | К трекам добавляется булево значение `isFavorite`, обозначающим наличие или отсутствие трека в любимых.
32 |
33 | ### Примеры :id=checkfavoritetracks-examples {docsify-ignore}
34 |
35 | 1. Оставить только треки плейлиста без лайков.
36 |
37 | ```js
38 | let tracks = Source.getPlaylistTracks('', 'id')
39 | Library.checkFavoriteTracks(tracks);
40 | tracks = tracks.filter(t => !t.isFavorite);
41 | ```
42 |
43 | ## deleteAlbums
44 |
45 | Удалить альбомы из библиотеки (лайки на альбомах).
46 |
47 | ### Аргументы :id=deletealbums-arguments {docsify-ignore}
48 |
49 | | Имя | Тип | Описание |
50 | |-----|-----|----------|
51 | | `albums` | Массив | Альбомы для удаления. Значимо только _id_. |
52 |
53 | ### Возврат :id=deletealbums-return {docsify-ignore}
54 |
55 | Нет возвращаемого значения.
56 |
57 | ## deleteFavoriteTracks
58 |
59 | Удалить треки из любимых (убрать лайки).
60 |
61 | ### Аргументы :id=deletefavoritetracks-arguments {docsify-ignore}
62 |
63 | | Имя | Тип | Описание |
64 | |-----|-----|----------|
65 | | `tracks` | Массив | Треки для удаления. Значимо только _id_. |
66 |
67 | ### Возврат :id=deletefavoritetracks-return {docsify-ignore}
68 |
69 | Нет возвращаемого значения.
70 |
71 | ### Примеры :id=deletefavoritetracks-examples {docsify-ignore}
72 |
73 | 1. Очистить все лайки
74 |
75 | ```js
76 | let savedTracks = Source.getSavedTracks();
77 | Library.deleteFavoriteTracks(savedTracks);
78 | ```
79 |
80 | ## followArtists
81 |
82 | Подписаться на исполнителей.
83 |
84 | ### Аргументы :id=followartists-arguments {docsify-ignore}
85 |
86 | | Имя | Тип | Описание |
87 | |-----|-----|----------|
88 | | `artists` | Массив | Исполнители для подписки. Значимо только _id_. |
89 |
90 | ### Возврат :id=followartists-return {docsify-ignore}
91 |
92 | Нет возвращаемого значения.
93 |
94 | ## followPlaylists
95 |
96 | Подписаться на плейлисты.
97 |
98 | ### Аргументы :id=followplaylists-arguments {docsify-ignore}
99 |
100 | | Имя | Тип | Описание |
101 | |-----|-----|----------|
102 | | `playlists` | Массив/Строка | Плейлисты (значимо только _id_) или строка с _id_, разделенные запятыми. |
103 |
104 | ### Возврат :id=followplaylists-return {docsify-ignore}
105 |
106 | Нет возвращаемого значения.
107 |
108 | ## saveAlbums
109 |
110 | Сохранить альбомы в библиотеку.
111 |
112 | ### Аргументы :id=savealbums-arguments {docsify-ignore}
113 |
114 | | Имя | Тип | Описание |
115 | |-----|-----|----------|
116 | | `albums` | Массив | Альбомы для добавления. Значимо только _id_. |
117 |
118 | ### Возврат :id=savealbums-return {docsify-ignore}
119 |
120 | Нет возвращаемого значения.
121 |
122 | ## saveFavoriteTracks
123 |
124 | Добавить треки в любимые (поставить лайк).
125 |
126 | ### Аргументы :id=savefavoritetracks-arguments {docsify-ignore}
127 |
128 | | Имя | Тип | Описание |
129 | |-----|-----|----------|
130 | | `tracks` | Массив | Треки для добавления. Значимо только _id_. |
131 |
132 | ### Возврат :id=savefavoritetracks-return {docsify-ignore}
133 |
134 | Нет возвращаемого значения.
135 |
136 | ## unfollowArtists
137 |
138 | Отписаться от исполнителей.
139 |
140 | ### Аргументы :id=unfollowartists-arguments {docsify-ignore}
141 |
142 | | Имя | Тип | Описание |
143 | |-----|-----|----------|
144 | | `artists` | Массив | Исполнители для отписки. Значимо только _id_. |
145 |
146 | ### Возврат :id=unfollowartists-return {docsify-ignore}
147 |
148 | Нет возвращаемого значения.
149 |
150 | ## unfollowPlaylists
151 |
152 | Отписаться от плейлистов.
153 |
154 | ### Аргументы :id=unfollowplaylists-arguments {docsify-ignore}
155 |
156 | | Имя | Тип | Описание |
157 | |-----|-----|----------|
158 | | `playlists` | Массив/Строка | Плейлисты (значимо только _id_) или строка с _id_, разделенные запятыми. |
159 |
160 | ### Возврат :id=unfollowplaylists-return {docsify-ignore}
161 |
162 | Нет возвращаемого значения.
163 |
--------------------------------------------------------------------------------
/docs/reference/order.md:
--------------------------------------------------------------------------------
1 | # Order
2 |
3 | Методы сортировки.
4 |
5 | | Метод | Тип результата | Краткое описание |
6 | |-------|----------------|------------------|
7 | | [reverse](/reference/order?id=reverse) | - | Отсортировать в обратном порядке. |
8 | | [separateArtists](/reference/order?id=separateartists) | - | Отделить треки одного и того же исполнителя друг от друга. |
9 | | [separateYears](/reference/order?id=separateyears) | Объект | Распределить треки по году релиза. |
10 | | [shuffle](/reference/order?id=shuffle) | - | Перемешать элементы массива случайным образом. |
11 | | [sort](/reference/order?id=sort) | - | Сортировать массив по метаданным. |
12 |
13 | ## reverse
14 |
15 | Отсортировать в обратном порядке.
16 |
17 | ### Аргументы :id=reverse-arguments {docsify-ignore}
18 |
19 | | Имя | Тип | Описание |
20 | |-----|-----|----------|
21 | | `array` | Массив | Сортируемый массив. |
22 |
23 | ### Возврат :id=reverse-return {docsify-ignore}
24 |
25 | Нет возвращаемого значения. Изменяет входной массив.
26 |
27 | ### Примеры :id=reverse-examples {docsify-ignore}
28 |
29 | 1. Обратная сортировка для массива чисел.
30 |
31 | ```js
32 | let array = [1, 2, 3, 4, 5, 6];
33 |
34 | Order.reverse(array);
35 | // результат 6, 5, 4, 3, 2, 1
36 |
37 | Order.reverse(array);
38 | // результат 1, 2, 3, 4, 5, 6
39 | ```
40 |
41 | 2. Обратная сортировка для массива треков.
42 |
43 | ```js
44 | let tracks = Source.getTracks(playlistArray);
45 | Order.reverse(tracks);
46 | ```
47 |
48 | ## separateArtists
49 |
50 | Отделить треки одного и того же исполнителя друг от друга. Треки, которые не удалось разместить будут исключены.
51 |
52 | ### Аргументы :id=separateartists-arguments {docsify-ignore}
53 |
54 | | Имя | Тип | Описание |
55 | |-----|-----|----------|
56 | | `tracks` | Массив | Сортируемые треки. |
57 | | `space` | Число | Минимальный отступ. |
58 | | `isRandom` | Булево | Если `true` выполняется случайная сортировка оригинального массива, что повлияет на порядок при разделении исполнителей. Если `false` без случайной сортировки. Тогда результат при одинаковых входных треках будет тоже одинаковыми. По умолчанию `false`. |
59 |
60 | ### Возврат :id=separateartists-return {docsify-ignore}
61 |
62 | Нет возвращаемого значения. Изменяет входной массив.
63 |
64 | ### Примеры :id=separateartists-examples {docsify-ignore}
65 |
66 | 1. Пример разделения
67 |
68 | ```js
69 | let array = ['cat', 'cat', 'dog', 'lion']
70 | Order.separateArtists(array, 1, false);
71 | // результат cat, dog, cat, lion
72 |
73 | array = ['cat', 'cat', 'dog', 'lion']
74 | Order.separateArtists(array, 1, false);
75 | // повторный вызов, результат тот же: cat, dog, cat, lion
76 |
77 | array = ['cat', 'cat', 'dog', 'lion']
78 | Order.separateArtists(array, 1, true);
79 | // повторный вызов и случайная сортировка: cat, lion, dog, cat
80 | ```
81 |
82 | 2. Разделить одного и того же исполнителя минимум двумя другими.
83 |
84 | ```js
85 | let tracks = Source.getTracks(playlistArray);
86 | Order.separateArtists(tracks, 2);
87 | ```
88 |
89 | ## separateYears
90 |
91 | Распределить треки по году релиза. Треки не сортируются по дате.
92 |
93 | ### Аргументы :id=separateyears-arguments {docsify-ignore}
94 |
95 | | Имя | Тип | Описание |
96 | |-----|-----|----------|
97 | | `tracks` | Массив | Сортируемые треки. |
98 |
99 | ### Возврат :id=separateyears-return {docsify-ignore}
100 |
101 | `tracksByYear` (объект) - массивы треков, распределенные по годам.
102 |
103 | ### Примеры :id=separateyears-examples {docsify-ignore}
104 |
105 | 1. Получить треки вышедшие только в 2020 году
106 |
107 | ```js
108 | let tracks2020 = Order.separateYears(tracks)['2020'];
109 | ```
110 |
111 | 2. Возможна ошибка, при которой среди треков нет указанного года. Выберите одно из: используйте [pickYear](/reference/selector?id=pickyear), подмените пустым массивом, проверьте условием.
112 |
113 | ```js
114 | // Подменить на пустой массив, если нет треков указанного года
115 | let tracks2020 = Order.separateYears(tracks)['2020'] || [];
116 |
117 | // Проверка через условие
118 | let tracksByYear = Order.separateYears(tracks);
119 | if (typeof tracksByYear['2020'] != 'undefined'){
120 | // треки есть
121 | } else {
122 | // треков нет
123 | }
124 | ```
125 |
126 | ## shuffle
127 |
128 | Перемешать элементы массива случайным образом.
129 |
130 | ### Аргументы :id=shuffle-arguments {docsify-ignore}
131 |
132 | | Имя | Тип | Описание |
133 | |-----|-----|----------|
134 | | `array` | Массив | Сортируемый массив. |
135 | | `factor` | Число | Коэффициент случайности. По умолчанию 1. Чем ближе к 0 тем меньше перестановок и ближе к исходной позиции. |
136 |
137 | ### Возврат :id=shuffle-return {docsify-ignore}
138 |
139 | Изменяет входной массив. Возвращает его же.
140 |
141 | ### Примеры :id=shuffle-examples {docsify-ignore}
142 |
143 | 1. Случайная сортировка на массиве чисел с разным коэффициентом. Запустите функцию `testShuffleWithFactor` несколько раз с разными значениями `factor` для наглядности.
144 |
145 | ```js
146 | function testShuffleWithFactor() {
147 | let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
148 | console.log('0.0:', Order.shuffle(array, 0.0)); // без изменений: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
149 | console.log('0.3:', Order.shuffle(array, 0.3)); // близкие перестановки: 0, 1, 3, 2, 6, 5, 4, 7, 8, 9
150 | console.log('0.6:', Order.shuffle(array, 0.6)); // результат: 3, 0, 8, 1, 2, 5, 4, 7, 6, 9
151 | console.log('1.0:', Order.shuffle(array)) ; // по умолчанию 1.0, результат: 6, 0, 9, 5, 7, 4, 8, 1, 2, 3
152 | }
153 | ```
154 |
155 | 1. Случайная сортировка на массиве треков.
156 |
157 | ```js
158 | let tracks = Source.getTracks(playlistArray);
159 | Order.shuffle(tracks);
160 | ```
161 |
162 | ## sort
163 |
164 | Сортировать массив по метаданным.
165 |
166 | !> Функция делает дополнительные запросы. Чтобы сократить число запросов, используйте ее после максимального сокращения массива треков другими способами. Подробнее в [rangeTracks](/reference/filter?id=rangetracks).
167 |
168 | ?> Если смешивается несколько источников треков, не у всех есть указанный ключ. Например, `played_at` есть только у истории прослушиваний.
169 |
170 | ### Аргументы :id=sort-arguments {docsify-ignore}
171 |
172 | | Имя | Тип | Описание |
173 | |-----|-----|----------|
174 | | `source` | Массив | Сортируемый массив. |
175 | | `pathKey` | Строка | Ключ сортировки. |
176 | | `direction` | Строка | Направление сортировки: _asc_ по возрастанию, _desc_ по убыванию. По умолчанию _asc_. |
177 |
178 | #### Ключ сортировки
179 |
180 | Ключ имеет формат `категория.данные`. [Описание метаданных](/reference/desc?id=Описание-параметров-объектов).
181 |
182 | | Категория | Данные |
183 | |-|-|
184 | | meta | name, popularity, duration_ms, explicit, added_at, played_at |
185 | | features | acousticness, danceability, energy, instrumentalness, liveness, loudness, speechiness, valence, tempo, key, mode, time_signature, duration_ms |
186 | | artist | popularity, followers, name |
187 | | album | popularity, name, release_date |
188 |
189 | ?> При использовании категории `features` общий поток любых треков становится приятнее.
190 |
191 | ### Возврат :id=sort-return {docsify-ignore}
192 |
193 | Нет возвращаемого значения. Изменяет входной массив.
194 |
195 | ### Примеры :id=sort-examples {docsify-ignore}
196 |
197 | 1. Сортировать по убывающей популярности исполнителей.
198 |
199 | ```js
200 | Order.sort(tracks, 'artist.popularity', 'desc');
201 | ```
202 |
203 | 2. Сортировать по возрастающей энергичности.
204 |
205 | ```js
206 | Order.sort(tracks, 'features.energy', 'asc');
207 | ```
208 |
--------------------------------------------------------------------------------
/docs/reference/player.md:
--------------------------------------------------------------------------------
1 | # Player
2 |
3 | Методы управления плеером.
4 |
5 | | Метод | Тип результата | Краткое описание |
6 | |-------|----------------|------------------|
7 | | [addToQueue](/reference/player?id=addtoqueue) | - | Добавить треки в очередь воспроизведения. |
8 | | [getAvailableDevices](/reference/player?id=getavailabledevices) | Массив | Получить список доступных устройств. |
9 | | [getPlayback](/reference/player?id=getplayback) | Объект | Получить данные плеера, включая играющий трек. |
10 | | [next](/reference/player?id=next) | - | Перейти к следующему треку в очереди. |
11 | | [pause](/reference/player?id=pause) | - | Остановить воспроизведение текущего плеера. |
12 | | [previous](/reference/player?id=previous) | - | Перейти к предыдущему треку в очереди. |
13 | | [resume](/reference/player?id=resume) | - | Продолжить воспроизведение текущей очереди или создать новую очередь. |
14 | | [setRepeatMode](/reference/player?id=setrepeatmode) | - | Установить режим повтора. |
15 | | [toggleShuffle](/reference/player?id=toggleshuffle) | - | Переключить режим перемешивания очереди. |
16 | | [transferPlayback](/reference/player?id=transferplayback) | - | Отправить текущий плейбэк на другое устройство. |
17 |
18 | ## addToQueue
19 |
20 | Добавить треки в очередь воспроизведения. Эквивалент _играть следующим_ в интерфейсе Spotify.
21 |
22 | ### Аргументы :id=addtoqueue-arguments {docsify-ignore}
23 |
24 | | Имя | Тип | Описание |
25 | |-----|-----|----------|
26 | | `items` | Массив/Объект | Массив треков или объект трека для добавления в очередь. Значимо только _id_. |
27 | | `deviceId` | Строка | Идентификатор устройства. Необязательно при активном воспроизведении. |
28 |
29 | ### Возврат :id=addtoqueue-return {docsify-ignore}
30 |
31 | Нет возвращаемого значения.
32 |
33 | ### Примеры :id=addtoqueue-examples {docsify-ignore}
34 |
35 | 1. Играть следующим последний добавленный лайк.
36 |
37 | ```js
38 | let tracks = Source.getSavedTracks(1);
39 | Player.addToQueue(tracks[0]);
40 | ```
41 |
42 | ## getAvailableDevices
43 |
44 | Получить список доступных устройств (подключены к Spotify в данный момент). Используйте для получения _id_ устройства. Значение из `getPlayback` достаточно быстро становится пустым при паузе.
45 |
46 | ### Аргументы :id=getavailabledevices-arguments {docsify-ignore}
47 |
48 | Аргументов нет.
49 |
50 | ### Возврат :id=getavailabledevices-return {docsify-ignore}
51 |
52 | `devices` (массив) - доступные устройства. [Пример массива](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-a-users-available-devices).
53 |
54 | ### Примеры :id=getavailabledevices-examples {docsify-ignore}
55 |
56 | 1. Отбор устройства по типу. `Smartphone` для телефона, `Computer` для ПК.
57 |
58 | ```js
59 | let device = Player.getAvailableDevices().find(d => d.type == 'Smartphone');
60 | // device.id
61 | ```
62 |
63 | ## getPlayback
64 |
65 | Получить данные плеера, включая играющий трек. При паузе достаточно быстро становится пустым.
66 |
67 | ### Аргументы :id=getplayback-arguments {docsify-ignore}
68 |
69 | Аргументов нет.
70 |
71 | ### Возврат :id=getplayback-return {docsify-ignore}
72 |
73 | `playback` (объект) - данные плеера. [Пример объекта](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-information-about-the-users-current-playback).
74 |
75 | ### Примеры :id=getplayback-examples {docsify-ignore}
76 |
77 | [Пример использования](https://github.com/Chimildic/goofy/discussions/102)
78 |
79 | ## next
80 |
81 | Перейти к следующему треку в очереди.
82 |
83 | ### Аргументы :id=next-arguments {docsify-ignore}
84 |
85 | Аргументов нет.
86 |
87 | ### Возврат :id=next-return {docsify-ignore}
88 |
89 | Нет возвращаемого значения.
90 |
91 | ## pause
92 |
93 | Остановить воспроизведение текущего плеера.
94 |
95 | ### Аргументы :id=pause-arguments {docsify-ignore}
96 |
97 | Аргументов нет.
98 |
99 | ### Возврат :id=pause-return {docsify-ignore}
100 |
101 | Нет возвращаемого значения.
102 |
103 | ## previous
104 |
105 | Перейти к предыдущему треку в очереди.
106 |
107 | ### Аргументы :id=previous-arguments {docsify-ignore}
108 |
109 | Аргументов нет.
110 |
111 | ### Возврат :id=previous-return {docsify-ignore}
112 |
113 | Нет возвращаемого значения.
114 |
115 | ## resume
116 |
117 | Продолжить воспроизведение текущей очереди или создать новую очередь.
118 |
119 | ### Аргументы :id=resume-arguments {docsify-ignore}
120 |
121 | | Имя | Тип | Описание |
122 | |-----|-----|----------|
123 | | `params` | Объект | Параметры очереди. |
124 |
125 | #### Параметры очереди :id=resume-arguments {docsify-ignore}
126 |
127 | | Имя | Тип | Описание |
128 | |-----|-----|----------|
129 | | `deviceId` | Строка | Идентификатор устройства. Необязательно при активном воспроизведении. |
130 | | `context_uri` | Строка | Воспроизведение по URI, например плейлист или альбом. |
131 | | `tracks` | Массив | Создать новую очередь с треками. Используется либо `context_uri`, либо `tracks`. |
132 | | `position_ms` | Число | Задать прогресс трека в миллисекундах. |
133 | | `offset` | Число | Задать активный трек в очереди `{ "position": 5 }`. Отсчет от нуля. |
134 |
135 | ### Возврат :id=resume-return {docsify-ignore}
136 |
137 | Нет возвращаемого значения.
138 |
139 | ### Примеры :id=resume-examples {docsify-ignore}
140 |
141 | 1. Продолжить воспроизведение после паузы.
142 |
143 | ```js
144 | Player.pause();
145 | Utilities.sleep(5000);
146 | Player.resume();
147 | ```
148 |
149 | 2. Создать очередь из любимых треков
150 |
151 | ```js
152 | let tracks = Source.getSavedTracks();
153 | Player.resume({
154 | tracks: tracks
155 | });
156 | ```
157 |
158 | 3. Воспроизвести плейлист по URI
159 |
160 | ```js
161 | let playlistId = '37i9dQZF1DWYmDNATMglFU';
162 | Player.resume({
163 | context_uri: `spotify:playlist:${playlistId}`,
164 | });
165 | ```
166 |
167 | ## setRepeatMode
168 |
169 | Установить режим повтора.
170 |
171 | ### Аргументы :id=setrepeatmode-arguments {docsify-ignore}
172 |
173 | | Имя | Тип | Описание |
174 | |-----|-----|----------|
175 | | `state` | Строка | При `track` повторяет текущий трек, при `context` текущую очередь, при `off` отключено. |
176 | | `deviceId` | Строка | Идентификатор устройства. Необязательно при активном воспроизведении. |
177 |
178 | ### Возврат :id=setrepeatmode-return {docsify-ignore}
179 |
180 | Нет возвращаемого значения.
181 |
182 | ## toggleShuffle
183 |
184 | Переключить режим перемешивания очереди.
185 |
186 | ### Аргументы :id=toggleshuffle-arguments {docsify-ignore}
187 |
188 | | Имя | Тип | Описание |
189 | |-----|-----|----------|
190 | | `state` | Строка | При `true` включает перемешивание, при `false` выключает. |
191 | | `deviceId` | Строка | Идентификатор устройства. Необязательно при активном воспроизведении. |
192 |
193 | ### Возврат :id=toggleshuffle-return {docsify-ignore}
194 |
195 | Нет возвращаемого значения.
196 |
197 | ## transferPlayback
198 |
199 | Отправить текущий плейбэк на другое устройство (т.е. очередь и играющий трек, [getPlayback](/reference/player?id=getplayback)).
200 |
201 | ### Аргументы :id=transferplayback-arguments {docsify-ignore}
202 |
203 | | Имя | Тип | Описание |
204 | |-----|-----|----------|
205 | | `deviceId` | Строка | _id_ нового устройства. Доступные значения можно получить, например, через [getAvailableDevices](/reference/player?id=getavailabledevices). |
206 | | `isPlay` | Булево | При `true` начнется воспроизведение на новом устройстве. Когда не указано или `false` состояние останется таким же, как на прошлом устройстве. |
207 |
208 | ### Возврат :id=transferplayback-return {docsify-ignore}
209 |
210 | Нет возвращаемого значения.
211 |
212 | ### Примеры :id=transferplayback-examples {docsify-ignore}
213 |
214 | [Пример использования](https://github.com/Chimildic/goofy/discussions/126)
215 |
--------------------------------------------------------------------------------
/docs/reference/playlist.md:
--------------------------------------------------------------------------------
1 | # Playlist
2 |
3 | Методы по созданию и управлению плейлистами.
4 |
5 | ### getDescription
6 |
7 | Возвращает строку вида: `Исполнитель 1, Исполнитель 2... и не только`.
8 |
9 | Аргументы
10 | - (массив) `tracks` - треки, из которых случайно выбираются исполнители.
11 | - (число) `limit` - количество случайно выбираемых исполнителей. По умолчанию 5.
12 |
13 | Пример 1 - Создать плейлист с описанием
14 | ```js
15 | let tracks = Source.getTracks(playlistArray);
16 | Playlist.saveWithReplace({
17 | id: 'abcd',
18 | name: 'Большой микс дня',
19 | tracks: tracks,
20 | description: Playlist.getDescription(tracks),
21 | });
22 | ```
23 |
24 | ### removeTracks
25 |
26 | Удаляет треки из плейлиста.
27 |
28 | Аргументы
29 | - (строка) `id` - id плейлиста.
30 | - (массив) `tracks` - массив треков.
31 |
32 | Пример 1 - Удалить лайки из плейлиста
33 | ```js
34 | let savedTracks = Source.getSavedTracks();
35 | Playlist.removeTracks('id', savedTracks);
36 | ```
37 |
38 | ### saveAsNew
39 |
40 | Создает плейлист. Каждый раз новый. Возвращает `id` созданного плейлиста.
41 |
42 | Аргументы
43 | - (объект) `data` - данные для создания плейлиста.
44 |
45 | Формат данных для создания плейлиста
46 | - (строка) `name` - название плейлиста, обязательно.
47 | - (массив) `tracks` - массив треков, обязательно.
48 | - (строка) `description` - описание плейлиста. До 300 символов.
49 | - (булево) `public` - если `false` плейлист будет приватным. По умолчанию `true`.
50 | - (строка) `sourceCover` - прямая ссылка на обложку (до 256 кб). Если указано, `randomCover` игнорируется.
51 | - (строка) `randomCover` - добавить случайную обложку при значении `once`. Без использования, стандартная мозайка от Spotify.
52 |
53 | Пример 1 - Создать публичный плейлист с любимыми треками без описания со случайной обложкой
54 | ```js
55 | let tracks = Source.getSavedTracks();
56 | Playlist.saveAsNew({
57 | name: 'Копия любимых треков',
58 | tracks: tracks,
59 | randomCover: 'once',
60 | // sourceCover: tracks[0].album.images[0].url,
61 | });
62 | ```
63 |
64 | Пример 2 - Создать приватный плейлист с недавней историей прослушиваний и описанием без обложки.
65 | ```js
66 | let tracks = RecentTracks.get(200);
67 | Playlist.saveAsNew({
68 | name: 'История прослушиваний',
69 | description: '200 недавно прослушанных треков'
70 | public: false,
71 | tracks: tracks,
72 | });
73 | ```
74 |
75 | ### saveWithAppend
76 |
77 | Добавляет треки к уже имеющимся в плейлисте. Обновляет остальные данные (название, описание). Если плейлиста еще нет, создает новый. Возвращает `id` плейлиста, в который добавлялись треки.
78 |
79 | Аргументы
80 | - (объект) `data` - данные о плейлисте. Формат данных о плейлисте согласно описанию [saveWithReplace](/reference/playlist?id=savewithreplace).
81 | - (строка) `position` - место добавления треков: начало `begin` или конец `end`. По умолчанию `end`.
82 |
83 | Пример 1 - Добавить треки в начало плейлиста.
84 | ```js
85 | let tracks = Source.getTracks(playlistArray);
86 | Playlist.saveWithAppend({
87 | id: 'fewf4t34tfwf4',
88 | name: 'Микс дня',
89 | tracks: tracks
90 | });
91 | ```
92 |
93 | Пример 2 - Добавить треки в конец плейлиста, обновить название и описание.
94 | ```js
95 | let tracks = Source.getTracks(playlistArray);
96 | Playlist.saveWithAppend({
97 | id: 'fewf4t34tfwf4',
98 | name: 'Новое название',
99 | description: 'Новое описание',
100 | tracks: tracks,
101 | });
102 | ```
103 |
104 | !> Если обновить название плейлиста без указания `id` будет создан новый плейлист. Потому что поиск не найден плейлист с новым названием.
105 |
106 | ### saveWithReplace
107 |
108 | Заменяет треки плейлиста. Обновляет остальные данные (название, описание). Если плейлиста еще нет, создает новый. Возвращает `id` плейлиста, в который добавлялись треки.
109 |
110 | Аргументы
111 | - (объект) `data` - данные о плейлисте.
112 |
113 | Формат данных о плейлисте
114 | - (строка) `id` - [идентификационный номер плейлиста](#идентификатор).
115 | - (строка) `name` - название плейлиста, обязательно.
116 | - (массив) `tracks` - массив треков, обязательно.
117 | - (строка) `description` - описание плейлиста. До 300 символов.
118 | - (булево) `public` - если `false` плейлист будет приватным. По умолчанию `true`.
119 | - (строка) `sourceCover` - прямая ссылка на обложку (до 256 кб). Если указано, `randomCover` игнорируется.
120 | - (строка) `randomCover` - если `once` добавит случайную обложку. При `update` каждый раз обновляет обложку. Без использования, стандартная мозайка от Spotify.
121 |
122 | ?> Рекомендуется всегда указывать `id`. Если `id` не указано, поиск по названию. Если такого плейлиста нет, создается новый.
123 |
124 | Пример 1 - Обновить содержимое плейлиста и обложку
125 | ```js
126 | let tracks = Source.getTracks(playlistArray);
127 | Playlist.saveWithReplace({
128 | id: 'fewf4t34tfwf4',
129 | name: 'Микс дня',
130 | description: 'Описание плейлиста',
131 | tracks: tracks,
132 | randomCover: 'update',
133 | // sourceCover: tracks[0].album.images[0].url,
134 | });
135 | ```
136 |
137 | Пример 2 - Обновить содержимое плейлиста из примера 1. Поиск по названию.
138 | ```js
139 | let tracks = RecentTracks.get();
140 | Playlist.saveWithReplace({
141 | name: 'История',
142 | description: 'Новое описание плейлиста',
143 | tracks: tracks,
144 | randomCover: 'update',
145 | });
146 | ```
147 |
148 | ### saveWithUpdate
149 |
150 | Обновляет плейлист или создает новый. Треки, которые есть в массиве, но нет в плейлисте - добавляются. Треки, которых нет в массиве, но есть в плейлисте - удаляются. То что есть и там, и там - сохраняется. Не затрагивается дата добавления треков. Невозможно применить сортировку, где перемешаны старые и новые треки (действует как `saveWithAppend`). Возвращает `id` плейлиста, в который добавлялись треки.
151 |
152 | Аргументы
153 | - (объект) `data` - данные о плейлисте, соответствует [saveWithReplace](/reference/playlist?id=savewithreplace).
154 |
155 | Дополнительный режим для `data`
156 | - (строка) `position` - место добавления треков: начало `begin` или конец `end`. По умолчанию `end`.
157 |
--------------------------------------------------------------------------------
/docs/reference/recenttracks.md:
--------------------------------------------------------------------------------
1 | # RecentTracks
2 |
3 | Методы работы с историей прослушиваний.
4 |
5 | | Метод | Тип результата | Краткое описание |
6 | |-------|----------------|------------------|
7 | | [appendTracks](/reference/filter?id=appendtracks) | - | Добавить массив треков к файлу истории прослушиваний. |
8 | | [compress](/reference/filter?id=compress) | - | Удалить незначимые данные о треках в накопительных файлах истории прослушиваний. |
9 | | [get](/reference/filter?id=get) | Массив | Получить треки истории прослушиваний. |
10 |
11 | ## appendTracks
12 |
13 | Добавить массив треков к файлу истории прослушиваний. Дата добавления в плейлист `added_at` становится датой прослушивания `played_at`. Если даты нет, устанавливается дата выполнения функции. Сортируется по дате прослушивания от свежих к более старым.
14 |
15 | !> Обратите внимание на ограничение в 60 тысяч треков для истории прослушиваний. Предел можно увеличить в файле `config`.
16 |
17 | ### Аргументы :id=appendtracks-arguments {docsify-ignore}
18 |
19 | | Имя | Тип | Описание |
20 | |-----|-----|----------|
21 | | `filename` | Строка | Имя файла истории прослушиваний: `SpotifyRecentTracks` или `LastfmRecentTracks`. |
22 | | `tracks` | Массив | Добавляемые треки. |
23 |
24 | ### Возврат :id=appendtracks-return {docsify-ignore}
25 |
26 | Нет возвращаемого значения.
27 |
28 | ### Примеры :id=appendtracks-examples {docsify-ignore}
29 |
30 | 1. Добавить в историю прослушиваний все любимые треки
31 |
32 | ```js
33 | let tracks = Source.getSavedTracks();
34 | RecentTracks.appendTracks('SpotifyRecentTracks', tracks);
35 | ```
36 |
37 | ## compress
38 |
39 | Удалить незначимые данные о треках в накопительных файлах истории прослушиваний в зависимости от [параметров](/config). Предварительно создается копия файла.
40 |
41 | ?> Используется для совместимости с прошлыми версиями библиотеки. Достаточно одного выполнения, чтобы сжать файлы истории прослушиваний. Новые треки истории сжимаются автоматически.
42 |
43 | ### Аргументы :id=compress-arguments {docsify-ignore}
44 |
45 | Аргументов нет.
46 |
47 | ### Возврат :id=compress-return {docsify-ignore}
48 |
49 | Нет возвращаемого значения.
50 |
51 | ## get
52 |
53 | Получить треки истории прослушиваний в зависимости от [параметров](/config). Сортировка по дате прослушивания от свежих к более старым.
54 |
55 | ### Аргументы :id=get-arguments {docsify-ignore}
56 |
57 | | Имя | Тип | Описание |
58 | |-----|-----|----------|
59 | | `limit` | Число | Если указано, ограничить количество возвращаемых треков. Если нет, все доступные. |
60 | | `isDedup` | Булево | При `true` удаляет дубликаты, при `false` оставляет как есть. По умолчанию `true` |
61 |
62 | #### Параметры истории прослушиваний
63 |
64 | | Включенный параметр | Возвращаемый массив |
65 | |-|-|
66 | | `ON_SPOTIFY_RECENT_TRACKS` | История прослушиваний только Spotify. Файл `SpotifyRecentTracks`. Дубли не удаляются. |
67 | | `ON_LASTFM_RECENT_TRACKS` | История прослушиваний только Lastfm. Файл `LastfmRecentTracks`. Дубли не удаляются. |
68 | | `ON_SPOTIFY_RECENT_TRACKS` и `ON_LASTFM_RECENT_TRACKS` | Объединение обоих источников с удалением дубликатов. Файл `BothRecentTracks`. |
69 |
70 | ### Возврат :id=get-return {docsify-ignore}
71 |
72 | `tracks` (массив) - треки истории прослушиваний.
73 |
74 | ### Примеры :id=get-examples {docsify-ignore}
75 |
76 | 1. Получить массив треков истории прослушиваний. Источник треков зависит от параметров.
77 |
78 | ```js
79 | let tracks = RecentTracks.get();
80 | ```
81 |
82 | 2. Получить 100 треков истории прослушиваний.
83 |
84 | ```js
85 | let tracks = RecentTracks.get(100);
86 | ```
87 |
--------------------------------------------------------------------------------
/docs/reference/search.md:
--------------------------------------------------------------------------------
1 | # Search
2 |
3 | Методы поиска. Используются другими функциями. Понадобится только для индивидуальных решений.
4 |
5 | ### find*
6 |
7 | Возвращает массив с результатами поиска по ключевому слову для типов: плейлист, трек, альбом, исполнитель.
8 |
9 | Аргументы
10 | - (массив) `keywords` - перечень ключевых слов, только строки. На каждое в результатах будет свой массив.
11 | - (число) `requestCount` - количество запросов на **одно** ключевое слово. На один запрос 50 объектов, если они есть. Максимум 40 запросов. По умолчанию 1.
12 |
13 | Пример 1 - Найти 100 плейлистов по слову `rain`
14 |
15 | ?> Лучшим способом будет функция [mineTracks](/reference/source?id=minetracks). Прямое использование модуля Search нужно для решений, которые не реализованы по умолчанию. Например, при [импорте треков с FM-радио](https://github.com/Chimildic/goofy/discussions/35).
16 |
17 | ```js
18 | let keywords = ['rain'];
19 | let playlists = Search.findPlaylists(keywords, 2);
20 | ```
21 |
22 | ### getNoFound
23 |
24 | Возвращает массив с ключевыми словами и типом поиска, для которых не нашлось результатов за текущее выполнение скрипта.
25 |
26 | Аргументов нет.
27 |
28 | ```js
29 | let noFound = Search.getNoFound();
30 | // структура: { type: '', keyword: '', item: {} }
31 | ```
32 |
33 | ### multisearch*
34 |
35 | Возвращается лучшее совпадение по ключевому словаму трека/исполнителя/альбома
36 |
37 | Аргументы
38 | - (массив) `items` - перебираемые элементы
39 | - (функция) `parseNameMethod` - коллбэк, вызываемый для каждого элемента. Должен вернуть строку, являющуюся ключевым словом для поиска.
40 |
41 | Пример 1 - Когда массив элементов с простым текстом
42 | ```js
43 | let keywords = ['skillet', 'skydive'];
44 | let artists = Search.multisearchArtists(keywords, (i) => i);
45 | ```
46 |
47 | Пример 2 - Когда массив элементов со сложной структурой
48 | ```js
49 | let tracks = Search.multisearchTracks(items, (item) => {
50 | return `${item.artist} ${item.title}`.formatName();
51 | });
52 | ```
--------------------------------------------------------------------------------
/docs/reference/selector.md:
--------------------------------------------------------------------------------
1 | # Selector
2 |
3 | Методы отбора и ветвления.
4 |
5 | ### isDayOfWeek
6 |
7 | Возвращает булево значение: `true` если сегодня день недели `strDay` и `false` если нет.
8 |
9 | Аргументы
10 | - (строка) `strDay` - день недели.
11 | - (строка) `locale` - локаль дня недели. По умолчанию `en-US`, для которой допустимы значения: `sunday`, `monday`, `tuesday`, `wednesday`, `thursday`, `friday`, `saturday`.
12 |
13 | Пример использования
14 | ```js
15 | if (Selector.isDayOfWeek('friday')){
16 | // сегодня пятница
17 | } else {
18 | // другой день недели
19 | }
20 | ```
21 |
22 | ### isDayOfWeekRu
23 |
24 | Возвращает булево значение: `true` если сегодня день недели `strDay` и `false` если нет. Значение дня недели кириллицей.
25 |
26 | Аргументы
27 | - (строка) `strDay` - день недели. Допустимые значения: `понедельник`, `вторник`, `среда`, `четверг`, `пятница`, `суббота`, `воскресенье`.
28 |
29 | Пример использования
30 | ```js
31 | if (Selector.isDayOfWeekRu('понедельник')){
32 | // сегодня понедельник
33 | } else if (Selector.isDayOfWeekRu('среда')) {
34 | // сегодня среда
35 | } else {
36 | // другой день недели
37 | }
38 | ```
39 |
40 | ### isWeekend
41 |
42 | Возвращает булево значение: `true` если сегодня суббота или пятница и `false` если нет.
43 |
44 | Аргументов нет.
45 |
46 | Пример использования
47 | ```js
48 | if (Selector.isWeekend()){
49 | // сегодня выходной
50 | } else {
51 | // будни
52 | }
53 | ```
54 |
55 | ### keepAllExceptFirst / sliceAllExceptFirst
56 |
57 | Изменяет / возвращает массив, состоящий из всех элементов массива `array` кроме `skipCount` первых.
58 |
59 | > Разница функций `keep*` и `slice*`:
60 | >
61 | > - `keep*` изменяет содержимое оригинального массива,
62 | > - `slice*` возвращает новый массив, не изменяя оригинала.
63 |
64 | Аргументы
65 | - (массив) `array` - массив, из которого берутся элементы.
66 | - (число) `skipCount` - количество пропускаемых элементов.
67 |
68 | Пример 1 - Получить все треки кроме первых 10.
69 | ```js
70 | let tracks = Source.getTracks(playlistArray);
71 | tracks = Selector.sliceAllExceptFirst(tracks, 10);
72 | ```
73 |
74 | ### keepAllExceptLast / sliceAllExceptLast
75 |
76 | Изменяет / возвращает массив, состоящий из всех элементов массива `array` кроме `skipCount` последних.
77 |
78 | Аргументы
79 | - (массив) `array` - массив, из которого берутся элементы.
80 | - (число) `skipCount` - количество пропускаемых элементов.
81 |
82 | Пример 1 - Получить все треки кроме последних 10.
83 | ```js
84 | let tracks = Source.getTracks(playlistArray);
85 | tracks = Selector.sliceAllExceptLast(tracks, 10);
86 | ```
87 |
88 | ### keepFirst / sliceFirst
89 |
90 | Изменяет / возвращает массив, состоящий из первых `count` элементов массива `array`.
91 |
92 | Аргументы
93 | - (массив) `array` - массив, из которого берутся элементы.
94 | - (число) `count` - количество элементов.
95 |
96 | Пример 1 - Получить первые 100 треков.
97 | ```js
98 | let tracks = Source.getTracks(playlistArray);
99 | tracks = Selector.sliceFirst(tracks, 100);
100 | ```
101 |
102 | ### keepLast / sliceLast
103 |
104 | Изменяет / возвращает массив, состоящий из последних `count` элементов массива `array`.
105 |
106 | Аргументы
107 | - (массив) `array` - массив, из которого берутся элементы.
108 | - (число) `count` - количество элементов.
109 |
110 | Пример 1 - Получить последние 100 треков.
111 | ```js
112 | let tracks = Source.getTracks(playlistArray);
113 | tracks = Selector.sliceLast(tracks, 100);
114 | ```
115 |
116 | ### keepNoLongerThan / sliceNoLongerThan
117 |
118 | Изменяет / возвращает массив треков с общей длительностью не более, чем `minutes` минут.
119 |
120 | Аргументы
121 | - (массив) `tracks` - исходный массив треков.
122 | - (число) `minutes` - количество минут.
123 |
124 | Пример 1 - Получить треки с общей продолжительностью не более, чем 60 минут.
125 | ```js
126 | let tracks = Source.getTracks(playlistArray);
127 | tracks = Selector.sliceNoLongerThan(tracks, 60);
128 | ```
129 |
130 | Пример 2 - Чтобы вычислить продолжительность треков из массива, используйте один из вариантов
131 | ```js
132 | let tracks = Source.getPlaylistTracks('', '37i9dQZF1DX5PcuIKocvtW');
133 | let duration_ms = tracks.reduce((d, t) => d + t.duration_ms, 0); // миллесекунды
134 | let duration_s = tracks.reduce((d, t) => d + t.duration_ms, 0) / 1000; // секунды
135 | let duration_min = tracks.reduce((d, t) => d + t.duration_ms, 0) / 1000 / 60; // минуты
136 | let duration_h = tracks.reduce((d, t) => d + t.duration_ms, 0) / 1000 / 60 / 60; // часы
137 | ```
138 |
139 | ### keepRandom / sliceRandom
140 |
141 | Изменяет / возвращает массив, состоящий из случайно отобранных элементов исходного массива.
142 |
143 | Аргументы
144 | - (массив) `array` - массив, из которого берутся элементы.
145 | - (число) `count` - количество случайно выбираемых элементов.
146 |
147 | Пример 1 - Получить 20 случайных треков.
148 | ```js
149 | let tracks = Source.getTracks(playlistArray);
150 | tracks = Selector.sliceRandom(tracks, 20);
151 | ```
152 |
153 | ### pickYear
154 |
155 | Возвращает массив треков, релиз которых был в указанном году. Если таких треков нет, выбирается ближайший год.
156 |
157 | Аргументы
158 | - (массив) `tracks` - треки, среди которых выбирать.
159 | - (строка) `year` - год релиза.
160 | - (число) `offset` - допустимое смещение для ближайшего года. По умолчанию 5.
161 |
162 | Пример 1 - Выбрать любимые треки, вышедшие в 2020 году
163 | ```js
164 | let tracks = Selector.pickYear(savedTracks, '2020');
165 | ```
166 |
167 | ### sliceCopy
168 |
169 | Возвращает новый массив, который является копией исходного массива.
170 |
171 | ?> Используйте создание копии, если в одном скрипте нужно выполнить разные действия над источником. Позволит ускорить время выполнения и не отправлять тех же запросов дважды.
172 |
173 | Аргументы
174 | - (массив) `array` - исходный массив, копию которого нужно создать.
175 |
176 | Пример 1 - Создать копию массива.
177 | ```js
178 | let tracks = Source.getTracks(playlistArray);
179 | let tracksCopy = Selector.sliceCopy(tracks);
180 | ```
181 |
--------------------------------------------------------------------------------
/docs/script/docsify-fix-pageload-scroll.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | function create() {
3 | return (hook) => {
4 | const TARGET_QUERY = 'id';
5 | const SCROLL_DELAY = 650;
6 |
7 | hook.doneEach(function () {
8 | if (!location.hash.includes('?')) return;
9 | let searchParams = new URLSearchParams(location.hash.split('?')[1]);
10 | let header = document.querySelector('#' + searchParams.get(TARGET_QUERY));
11 | header && setTimeout(() => header.scrollIntoView(), SCROLL_DELAY);
12 | });
13 | };
14 | }
15 |
16 | if (typeof $docsify === 'object') {
17 | $docsify.plugins = [].concat(create(), $docsify.plugins);
18 | }
19 | })();
20 |
--------------------------------------------------------------------------------
/docs/script/docsify-sidebar-collapse.js:
--------------------------------------------------------------------------------
1 | (function (global, factory) {
2 | typeof exports === 'object' && typeof module !== 'undefined'
3 | ? factory()
4 | : typeof define === 'function' && define.amd
5 | ? define(factory)
6 | : factory();
7 | })(this, function () {
8 | 'use strict';
9 |
10 | function styleInject(css, ref) {
11 | if (ref === void 0) ref = {};
12 | var insertAt = ref.insertAt;
13 |
14 | if (!css || typeof document === 'undefined') {
15 | return;
16 | }
17 |
18 | var head = document.head || document.getElementsByTagName('head')[0];
19 | var style = document.createElement('style');
20 | style.type = 'text/css';
21 |
22 | if (insertAt === 'top') {
23 | if (head.firstChild) {
24 | head.insertBefore(style, head.firstChild);
25 | } else {
26 | head.appendChild(style);
27 | }
28 | } else {
29 | head.appendChild(style);
30 | }
31 |
32 | if (style.styleSheet) {
33 | style.styleSheet.cssText = css;
34 | } else {
35 | style.appendChild(document.createTextNode(css));
36 | }
37 | }
38 |
39 | var css =
40 | '.sidebar-nav > ul > li ul {\n display: none;\n}\n\n.app-sub-sidebar {\n display: none;\n}\n\n.app-sub-sidebar.open {\n display: block;\n}\n\n.sidebar-nav .open > ul:not(.app-sub-sidebar),\n.sidebar-nav .active:not(.collapse) > ul {\n display: block;\n}\n\n/* 抖动 */\n.sidebar-nav li.open:not(.collapse) > ul {\n display: block;\n}\n\n.active + ul.app-sub-sidebar {\n display: block;\n}\n';
41 | styleInject(css);
42 |
43 | function sidebarCollapsePlugin(hook, vm) {
44 | hook.doneEach(function (html, next) {
45 | var activeNode = getActiveNode();
46 | openActiveToRoot(activeNode);
47 | addFolderFileClass();
48 | syncScrollTop(activeNode);
49 | next(html);
50 | });
51 | hook.ready(function (html, next) {
52 | document.querySelector('.sidebar-nav').addEventListener('click', handleMenuClick);
53 | });
54 | }
55 |
56 | function init() {
57 | document.addEventListener('scroll', scrollSyncMenuStatus);
58 | }
59 |
60 | var lastTop; // 侧边栏滚动状态
61 |
62 | function syncScrollTop(activeNode) {
63 | if (activeNode && lastTop != undefined) {
64 | var curTop = activeNode.getBoundingClientRect().top;
65 | document.querySelector('.sidebar').scrollBy(0, curTop - lastTop);
66 | }
67 | }
68 |
69 | function scrollSyncMenuStatus() {
70 | requestAnimationFrame(function () {
71 | var el = document.querySelector('.app-sub-sidebar > .active');
72 |
73 | if (el) {
74 | el.parentNode.parentNode.querySelectorAll('.app-sub-sidebar').forEach(function (dom) {
75 | return dom.classList.remove('open');
76 | });
77 |
78 | while (el.parentNode.classList.contains('app-sub-sidebar')) {
79 | if (el.parentNode.classList.contains('open')) {
80 | break;
81 | } else {
82 | el.parentNode.classList.add('open');
83 | el = el.parentNode;
84 | }
85 | }
86 | }
87 | });
88 | }
89 |
90 | function handleMenuClick(e) {
91 | lastTop = e.target.getBoundingClientRect().top;
92 | var newActiveNode = findTagParent(e.target, 'LI', 2);
93 | if (!newActiveNode) return;
94 |
95 | if (newActiveNode.classList.contains('open')) {
96 | //newActiveNode.classList.add('collapse');
97 | newActiveNode.classList.remove('open');
98 |
99 | setTimeout(function () {
100 |
101 | }, 0);
102 | } else {
103 | //document.querySelectorAll('.file .collapse').forEach((item) => item.classList.remove('collapse'));
104 | removeOpenToRoot(getActiveNode());
105 | openActiveToRoot(newActiveNode);
106 | }
107 |
108 | syncScrollTop(newActiveNode);
109 | }
110 |
111 | function getActiveNode() {
112 | var node = document.querySelector('.sidebar-nav .active');
113 |
114 | if (!node) {
115 | var curLink = document.querySelector(
116 | '.sidebar-nav a[href="'.concat(decodeURIComponent(location.hash).replace(/ /gi, '%20'), '"]')
117 | );
118 | node = findTagParent(curLink, 'LI', 2);
119 |
120 | if (node) {
121 | node.classList.add('active');
122 | }
123 | }
124 |
125 | return node;
126 | }
127 |
128 | function openActiveToRoot(node) {
129 | if (node) {
130 | node.classList.add('open', 'active');
131 |
132 | while (node && node.className !== 'sidebar-nav' && node.parentNode) {
133 | if (node.parentNode.tagName === 'LI' || node.parentNode.className === 'app-sub-sidebar') {
134 | node.parentNode.classList.add('open');
135 | }
136 |
137 | node = node.parentNode;
138 | }
139 | }
140 | }
141 |
142 | function removeOpenToRoot(node) {
143 | if (node) {
144 | node.classList.remove('open', 'active');
145 |
146 | while (node && node.className !== 'sidebar-nav' && node.parentNode) {
147 | if (node.parentNode.tagName === 'LI' || node.parentNode.className === 'app-sub-sidebar') {
148 | node.parentNode.classList.remove('open');
149 | }
150 |
151 | node = node.parentNode;
152 | }
153 | }
154 | }
155 |
156 | function findTagParent(curNode, tagName, level) {
157 | if (curNode && curNode.tagName === tagName) return curNode;
158 | var l = 0;
159 |
160 | while (curNode) {
161 | l++;
162 | if (l > level) return;
163 |
164 | if (curNode.parentNode.tagName === tagName) {
165 | return curNode.parentNode;
166 | }
167 |
168 | curNode = curNode.parentNode;
169 | }
170 | }
171 |
172 | function addFolderFileClass() {
173 | document.querySelectorAll('.sidebar-nav li').forEach(function (li) {
174 | if (li.querySelector('ul:not(.app-sub-sidebar)')) {
175 | li.classList.add('folder');
176 | } else {
177 | li.classList.add('file');
178 | }
179 | });
180 | }
181 |
182 | init();
183 |
184 | // var css$1 =
185 | // '@media screen and (max-width: 768px) {\n /* 移动端适配 */\n .markdown-section {\n max-width: none;\n padding: 16px;\n }\n /* 改变原来按钮热区大小 */\n .sidebar-toggle {\n padding: 0 0 10px 10px;\n }\n /* my pin */\n .sidebar-pin {\n appearance: none;\n outline: none;\n position: fixed;\n bottom: 0;\n border: none;\n width: 40px;\n height: 40px;\n background: transparent;\n }\n}\n';
186 | // styleInject(css$1);
187 |
188 | var PIN = 'DOCSIFY_SIDEBAR_PIN_FLAG';
189 |
190 | function init$1() {
191 | // 响应式尺寸 @media screen and (max-width: 768px)
192 | if (document.documentElement.clientWidth > 768) return;
193 | localStorage.setItem(PIN, false); // 添加覆盖标签
194 |
195 | var btn = document.createElement('button');
196 | btn.classList.add('sidebar-pin');
197 | btn.onclick = togglePin;
198 | document.body.append(btn);
199 | window.addEventListener('load', function () {
200 | var content = document.querySelector('.content'); // 点击内容区域收起侧边栏
201 |
202 | document.body.onclick = content.onclick = function (e) {
203 | if (e.target === document.body || e.currentTarget === content) {
204 | if (localStorage.getItem(PIN) === 'true') {
205 | togglePin();
206 | }
207 | }
208 | };
209 | });
210 | }
211 |
212 | function togglePin() {
213 | var pin = localStorage.getItem(PIN);
214 | pin = pin === 'true';
215 | localStorage.setItem(PIN, !pin);
216 |
217 | if (pin) {
218 | document.querySelector('.sidebar').style.transform = 'translateX(0)';
219 | document.querySelector('.content').style.transform = 'translateX(0)';
220 | } else {
221 | document.querySelector('.sidebar').style.transform = 'translateX(300px)';
222 | document.querySelector('.content').style.transform = 'translateX(300px)';
223 | }
224 | }
225 |
226 | init$1();
227 |
228 | function install() {
229 | if (!window.$docsify) {
230 | console.error('这是一个docsify插件,请先引用docsify库!');
231 | } else {
232 | for (var _len = arguments.length, plugins = new Array(_len), _key = 0; _key < _len; _key++) {
233 | plugins[_key] = arguments[_key];
234 | }
235 |
236 | $docsify.plugins = plugins.concat($docsify.plugins || []);
237 | }
238 | }
239 |
240 | install(sidebarCollapsePlugin);
241 | });
--------------------------------------------------------------------------------
/docs/style/custom.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --content-max-width: 65rem;
3 | --notice-tip-border-color: var(--color-accent);
4 | --link-color: var(--theme-color);
5 | }
6 |
7 | .app-nav {
8 | display: flex;
9 | justify-content: flex-end;
10 | }
11 |
12 | @media (min-width: 900px) {
13 | p, ul {
14 | text-align: justify;
15 | }
16 | }
--------------------------------------------------------------------------------
/docs/style/dark-theme.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --code-theme-background: #222;
3 | --code-theme-comment: #516e7a;
4 | --code-theme-function: #f07178;
5 | --code-theme-keyword: #c2e78c;
6 | --code-theme-operator: #ffcb6b;
7 | --code-theme-punctuation: #89ddff;
8 | --code-theme-selector: #ffcb6b;
9 | --code-theme-tag: #f07178;
10 | --code-theme-text: #f3f3f3;
11 | --code-theme-variable: #ffcb6b;
12 | }
13 | :root {
14 | --color-accent: rgba(12, 209, 144, 0.8);
15 | --theme-lightness: 45%;
16 | --theme-hue: 160;
17 | --theme-saturation: 90%;
18 | --theme-alpha: 0.95;
19 | --mono-hue: 200;
20 | --mono-saturation: 18%;
21 | --mono-shade3: hsl(var(--mono-hue), var(--mono-saturation), 13%);
22 | --mono-shade2: hsl(var(--mono-hue), var(--mono-saturation), 15%);
23 | --mono-shade1: hsl(var(--mono-hue), var(--mono-saturation), 17%);
24 | --mono-base: hsl(var(--mono-hue), var(--mono-saturation), 19%);
25 | --mono-tint1: hsl(var(--mono-hue), var(--mono-saturation), 25%);
26 | --mono-tint2: hsl(var(--mono-hue), var(--mono-saturation), 35%);
27 | --mono-tint3: hsl(var(--mono-hue), var(--mono-saturation), 43%);
28 | --spinner-track-color: rgba(255, 255, 255, 0.15);
29 | --base-background-color: var(--mono-base);
30 | --base-color: #d3d3d3;
31 | --hr-border: 1px solid var(--mono-tint2);
32 | --mark-background: #ffcb6b;
33 | --mark-color: var(--base-background-color);
34 | --selection-color: rgba(94, 131, 175, 0.75);
35 | --blockquote-background: var(--mono-shade2);
36 | --code-inline-background: var(--mono-tint1);
37 | --code-theme-background: var(--mono-shade2);
38 | --heading-color: #fff;
39 | --heading-h2-border-color: var(--mono-tint2);
40 | --kbd-background: var(--mono-shade2);
41 | --kbd-border: none;
42 | --kbd-color: var(--strong-color);
43 | --notice-important-background: var(--mono-shade2);
44 | --notice-tip-background: var(--mono-shade2);
45 | --table-cell-border-color: var(--mono-tint1);
46 | --table-row-odd-background: var(--mono-shade2);
47 | --cover-background-color: var(--base-background-color);
48 | --cover-background-image: radial-gradient(ellipse at center bottom, var(--mono-tint3), transparent);
49 | --cover-blockquote-color: var(--mark-background);
50 | --cover-button-border: 1px solid var(--mono-tint3);
51 | --cover-button-color: #fff;
52 | --navbar-menu-background: var(--mono-tint1);
53 | --navbar-menu-box-shadow: rgba(0, 0, 0, 0.05) 0 0 1px, rgba(0, 0, 0, 0.05) 0 1px 2px, rgba(0, 0, 0, 0.05) 0 2px 4px,
54 | rgba(0, 0, 0, 0.05) 0 4px 8px, rgba(0, 0, 0, 0.05) 0 8px 16px, rgba(0, 0, 0, 0.05) 0 16px 32px;
55 | --copycode-background: var(--mono-tint1);
56 | --copycode-color: #fff;
57 | --docsifytabs-border-color: var(--mono-tint2);
58 | --docsifytabs-tab-background: var(--mono-shade1);
59 | --docsifytabs-tab-color: var(--mono-tint2);
60 | --pagination-border-top: 1px solid var(--mono-tint2);
61 | --pagination-title-color: #fff;
62 | --search-input-background-color: var(--mono-shade2);
63 | --search-input-background-image: url("data:image/svg+xml,%3Csvg height='20px' width='20px' viewBox='0 0 24 24' fill='none' stroke='rgba(255, 255, 255, 0.3)' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='xMidYMid meet' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='10.5' cy='10.5' r='7.5' vector-effect='non-scaling-stroke'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='15.8' y2='15.8' vector-effect='non-scaling-stroke'%3E%3C/line%3E%3C/svg%3E");
64 | --search-input-border-color: var(--mono-tint1);
65 | --search-input-placeholder-color: rgba(255, 255, 255, 0.4);
66 | --search-clear-icon-color1: rgba(255, 255, 255, 0.3);
67 | --sidebar-background: var(--mono-shade1);
68 | --sidebar-border-color: var(--mono-tint1);
69 | --sidebar-nav-pagelink-background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='7' height='11.2' viewBox='0 0 7 11.2'%3E%3Cpath d='M1.5 1.5l4 4.1 -4 4.1' stroke-width='1.5' stroke='rgb%2873, 93, 104%29' fill='none' stroke-linecap='square' stroke-linejoin='miter' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E");
70 | }
71 |
--------------------------------------------------------------------------------
/docs/style/light-theme.css:
--------------------------------------------------------------------------------
1 | :root{
2 | --color-accent: rgba(10, 194, 133, 1);
3 | --theme-hue: 160;
4 | --theme-saturation: 90%;
5 | --theme-lightness: 40%;
6 | --theme-alpha: 1;
7 | }
--------------------------------------------------------------------------------
/docs/template.md:
--------------------------------------------------------------------------------
1 | # Список шаблонов
2 |
3 | Перед использованием шаблонов рекомендуется ознакомиться с обучением по созданию [первого плейлиста](/first-playlist). Значительно упростит работу с шаблонами, ответит на частые вопросы. Дополнительные шаблоны найдете на [форуме GitHub](https://github.com/Chimildic/goofy/discussions) и [4PDA](https://4pda.to/forum/index.php?s=&showtopic=715234&view=findpost&p=101196000).
4 |
5 | ## Незнакомый сет
6 | ```js
7 | /**
8 | * Объединить треки из личных миксов в один плейлист
9 | * Удалить дубликаты, историю прослушиваний, лайки
10 | */
11 | function updateUnknownSet() {
12 | let banTracks = [];
13 | let savedTracks = Source.getSavedTracks();
14 | let recentTracks = RecentTracks.get(1000);
15 | Combiner.push(banTracks, savedTracks, recentTracks);
16 |
17 | // Вставьте id плейлистов, которые берутся из ссылки или URI
18 | // Пример: https://chimildic.github.io/goofy/#/reference/desc?id=Идентификатор
19 | let onlyForYouTracks = Source.getTracks([
20 | { name: 'Микс дня 1', id: 'вашеId' },
21 | { name: 'Микс дня 2', id: 'вашеId' },
22 | { name: 'Микс дня 3', id: 'вашеId' },
23 | { name: 'Микс дня 4', id: 'вашеId' },
24 | { name: 'Микс дня 5', id: 'вашеId' },
25 | { name: 'Микс дня 6', id: 'вашеId' },
26 | { name: 'Радар новинок', id: 'вашеId' },
27 | { name: 'Открытия недели', id: 'вашеId' },
28 | ]);
29 |
30 | Filter.dedupTracks(onlyForYouTracks);
31 | Filter.dedupArtists(onlyForYouTracks);
32 | Filter.removeTracks(onlyForYouTracks, banTracks);
33 | Filter.matchOriginalOnly(onlyForYouTracks);
34 | Filter.matchExceptRu(onlyForYouTracks);
35 |
36 | // Пример удаления исполнителей, если ведете бан-плейлист
37 | // let banArtists = Source.getPlaylistTracks('', 'вашеId');
38 | // Filter.removeArtists(onlyForYouTracks, banArtists);
39 |
40 | Filter.rangeTracks(onlyForYouTracks, {
41 | artist: {
42 | ban_genres: ['rap', 'r&b', 'metal', 'anime', 'soul', 'blues', 'punk'],
43 | },
44 | });
45 |
46 | // Подставьте id созданного плейлиста, после первого запуска кода, удалите комментарий
47 | Playlist.saveWithReplace({
48 | // id: 'вашеId',
49 | name: 'Незнакомый сет',
50 | tracks: Selector.sliceRandom(onlyForYouTracks, 60),
51 | description: Playlist.getDescription(onlyForYouTracks),
52 | randomCover: 'update',
53 | });
54 | }
55 | ```
56 |
57 | ## Новые релизы
58 | ```js
59 | /**
60 | * Сбор новых релизов от отслеживаемых исполнителей
61 | */
62 | function updateNewReleases() {
63 | let newReleases = Source.getRecentReleasesByArtists({
64 | artists: Source.getArtists({ followed_include: true, }),
65 | date: { sinceDays: 7, beforeDays: 0 }, // за неделю, измените по необходимости
66 | type: ['album', 'single'],
67 | });
68 | Playlist.saveWithReplace({
69 | name: 'Новые релизы',
70 | tracks: newReleases,
71 | })
72 | }
73 | ```
74 |
75 | ## Новые релизы по частям
76 | ```js
77 | /**
78 | * Когда отслеживаемых исполнителей очень много, есть шанс достигнуть лимита на время выполнения или получить паузу от Spotify на сутки.
79 | * Можно разделить исполнителей на чанки. Например, установив триггер "каждый час" при размере чанка 100 за сутки проверится 2400 исполнителей.
80 | * Функция накапливает релизы в кэш. При желании можно подмешивать их в другие плейлисты.
81 | */
82 | function chunkDiscoverRecentReleases() {
83 | const CHUNK_SIZE = 100 // Количество проверяемых исполнителей за один запуск
84 | const FA_FILENAME = 'ChunkFollowedArtists.json'
85 | const RR_FILENAME = 'ChunkDiscoverRecentReleases.json'
86 |
87 | let followedArtists = Cache.read(FA_FILENAME)
88 | if (followedArtists.length == 0) {
89 | followedArtists = Source.getArtists({ followed_include: true })
90 | Cache.write(FA_FILENAME, followedArtists)
91 | }
92 | if (followedArtists.length > 0) {
93 | discover()
94 | saveWithReplace() // опциальное сохранение релизов в плейлист
95 | }
96 |
97 | function discover() {
98 | let discoverableArtists = followedArtists.splice(0, CHUNK_SIZE)
99 | Cache.write(FA_FILENAME, followedArtists)
100 |
101 | let remoteTracks = Source.getRecentReleasesByArtists({
102 | artists: discoverableArtists,
103 | date: { sinceDays: 1, beforeDays: 0 },
104 | type: ['album', 'single'],
105 | isFlat: true,
106 | })
107 |
108 | if (remoteTracks.length > 0) {
109 | Cache.compressTracks(remoteTracks)
110 | let combinedTracks = Combiner.push(remoteTracks, Cache.read(RR_FILENAME))
111 | Filter.dedupTracks(combinedTracks)
112 | Order.sort(combinedTracks, 'album.release_date', 'desc')
113 | Cache.write(RR_FILENAME, combinedTracks)
114 | }
115 | }
116 |
117 | function saveWithReplace() {
118 | Playlist.saveWithReplace({
119 | name: 'Новые релизы',
120 | tracks: Cache.read(RR_FILENAME),
121 | randomCover: 'update',
122 | })
123 | }
124 | }
125 | ```
126 |
127 | ## Открытия с альбомов
128 | ```js
129 | /**
130 | * Популярные треки с альбомов, в которых уже есть известные вам любимые треки
131 | */
132 | function updateDiscoveryAlbums() {
133 | const LIMIT_TRACKS = 20;
134 | const LIMIT_ALB_TRACK = 1;
135 |
136 | let recentTracks = RecentTracks.get(3000);
137 | let savedTracks = Source.getSavedTracks();
138 | let banTracks = Combiner.push([], recentTracks, savedTracks);
139 | let banArtists = Selector.sliceCopy(recentTracks);
140 | Filter.rangeDateRel(banArtists, 2, 0);
141 |
142 | let tracks = savedTracks;
143 | Order.shuffle(tracks);
144 |
145 | let recomTracks = [];
146 | for (let i = 0; i < tracks.length; i++) {
147 | if (tracks[i].album.album_type == 'compilation'
148 | || tracks[i].album.total_tracks == 1) {
149 | continue;
150 | }
151 | let albumTracks = Source.getAlbumTracks(tracks[i].album);
152 | Filter.matchOriginalOnly(albumTracks);
153 | Filter.removeArtists(albumTracks, banArtists);
154 | Filter.removeTracks(albumTracks, banTracks);
155 | if (albumTracks.length == 0) {
156 | continue;
157 | }
158 |
159 | Order.sort(albumTracks, 'meta.popularity', 'desc');
160 | Selector.keepFirst(albumTracks, LIMIT_ALB_TRACK);
161 | Combiner.push(recomTracks, albumTracks);
162 |
163 | Filter.dedupTracks(recomTracks);
164 | if (recomTracks.length >= LIMIT_TRACKS) {
165 | break;
166 | }
167 | }
168 |
169 | Playlist.saveWithReplace({
170 | name: 'Открытия с альбомов',
171 | description: 'Эти треки должны тебе понравится!',
172 | tracks: Selector.sliceFirst(recomTracks, LIMIT_TRACKS),
173 | randomCover: 'update',
174 | });
175 | }
176 | ```
177 |
178 | ## Любимо и забыто
179 | ```js
180 | /**
181 | * Собрать любимые треки, которые давно не прослушивались и добавлены больше месяца назад
182 | * Рекомендуется использовать после накопления хотя бы небольшой истории прослушиваний
183 | */
184 | function updateSavedAndForgot(){
185 | let recentTracks = RecentTracks.get(3000);
186 | let savedTracks = Source.getSavedTracks();
187 | Filter.removeTracks(savedTracks, recentTracks);
188 |
189 | let startDate = new Date('2006-01-01');
190 | let endDate = Filter.getDateRel(30, 'endDay');
191 | Filter.rangeDateAbs(savedTracks, startDate, endDate);
192 |
193 | Selector.keepRandom(savedTracks, 20);
194 | Order.sort(savedTracks, 'meta.added_at', 'asc');
195 |
196 | Playlist.saveWithReplace({
197 | // id: 'вашеId',
198 | name: 'Любимо и забыто',
199 | tracks: savedTracks,
200 | randomCover: 'update',
201 | });
202 | }
203 | ```
204 |
205 | ## Отслеживание обновлений
206 | ```js
207 | /**
208 | * Собрать из отслеживаемых плейлистов треки, которые были добавлены за неделю
209 | */
210 | function updateFollowedTracks(){
211 | let followedTracks = Source.getFollowedTracks({
212 | type: 'followed',
213 | });
214 | // При необходимости измените период
215 | Filter.rangeDateRel(followedTracks, 7, 1);
216 |
217 | // Добавьте Order и Selector для ограничения количества
218 |
219 | Playlist.saveWithReplace({
220 | // id: 'вашеId',
221 | name: 'Отслеживание обновлений',
222 | tracks: followedTracks,
223 | randomCover: 'update',
224 | });
225 | }
226 | ```
227 |
228 | ## Новинки редакций
229 | ```js
230 | /**
231 | * Собрать новые релизы из подборок разных редакций (кураторов) за неделю
232 | */
233 | function updateNewRelease(){
234 | let recentTracks = RecentTracks.get(2000);
235 | let newReleaseTracks = Source.getTracks([
236 | // Популярные редакции
237 | { name: 'All New Indie', id: '37i9dQZF1DXdbXrPNafg9d' },
238 | { name: 'New music friday', id: '37i9dQZF1DX4JAvHpjipBk' },
239 | { name: 'NMEs Best New Tracks', id: '4NzWle6sDBwHLQ1tuqLKhp' },
240 | { name: 'Best New Music by Complex', id: '5PKZSKuHP4d27SXO5fB9Wl' },
241 | { name: 'MTV PUSH: Radar', id: '1RpijnCwXVGB2fxMA8km5K' },
242 | { name: 'Pop n Fresh by Spotify', id: '37i9dQZF1DX50KNyX4Fc9O' },
243 | { name: 'New Music Friday UK', id: '37i9dQZF1DX4W3aJJYCDfV' },
244 | { name: 'New This Week by Topsify', id: '4f0IMCLd3iciiLR4V6qXcp' },
245 | { name: 'Pop Rising by Topsify', id: '37i9dQZF1DWUa8ZRTfalHk' },
246 |
247 | // Менее популярные редакции
248 | { name: 'Disco Naivete', id: '4c6G93bHqsUbwqlqRDND9k' },
249 | { name: 'The Line Of Best Fit', id: '5359l8Co8qztllR0Mxk4Zv' },
250 | { name: 'Going Solo', id: '1ozCM0k4h6vrMlAzNaJCyy' },
251 | { name: '[PIAS] Monday', id: '59y1SSfAYf2DE4PmHhwNh1' },
252 | { name: 'undercurrents', id: '37i9dQZF1DX9myttyycIxA' },
253 | { name: 'XL Play', id: '1IUF5q4IvkjylMhd9P0urE' },
254 | { name: 'HumanHuman Most Promising', id: '5VMDrQb7imexrTLjLVjbnO' },
255 | { name: 'ESNS Chart', id: '72qhgUjoFVONkcQcBNQYcY' },
256 | ]);
257 |
258 | Filter.dedupTracks(newReleaseTracks);
259 | Filter.rangeDateRel(newReleaseTracks, 7, 1);
260 | Filter.removeTracks(newReleaseTracks, recentTracks);
261 | Filter.matchExceptMix(newReleaseTracks);
262 | Filter.matchExceptRu(newReleaseTracks);
263 | Filter.rangeTracks(newReleaseTracks, {
264 | meta: {
265 | popularity: { min: 35, max: 100 },
266 | },
267 | artist: {
268 | ban_genres: ['pop', 'hip hop', 'rap', 'r&b', 'blues', 'punk', 'hollywood', 'latin', 'african', 'house'],
269 | },
270 | });
271 |
272 | Order.sort(newReleaseTracks, 'meta.popularity', 'desc');
273 | Selector.keepFirst(newReleaseTracks, 60);
274 |
275 | Playlist.saveWithReplace({
276 | // id: 'вашеId',
277 | name: 'Новинки редакций',
278 | tracks: newReleaseTracks,
279 | randomCover: 'update',
280 | });
281 | }
282 | ```
283 |
284 | ## Копилка
285 | ```js
286 | /**
287 | * Накапливать треки из "Радара новинок", прослушанные треки удаляются
288 | */
289 | function updateCollectorPlaylist(){
290 | // После первого создания плейлиста добавьте id копилки и удалите комментарии
291 | // const COLLECTOR_PLAYLIST_ID = '';
292 | let recentTracks = RecentTracks.get(1000);
293 | let newTracks = Source.getPlaylistTracks('Радар новинок', 'вашеId');
294 |
295 | // let currentTracks = Source.getPlaylistTracks('Копилка: Радар новинок', COLLECTOR_PLAYLIST_ID);
296 | // Combiner.push(newTracks, currentTracks);
297 |
298 | Filter.dedupTracks(newTracks);
299 | Filter.removeTracks(newTracks, recentTracks);
300 |
301 | Playlist.saveWithReplace({
302 | // id: COLLECTOR_PLAYLIST_ID,
303 | name: 'Копилка: Радар новинок',
304 | tracks: newTracks,
305 | randomCover: 'update',
306 | });
307 | }
308 | ```
--------------------------------------------------------------------------------
/docs/tuning.md:
--------------------------------------------------------------------------------
1 | # Расширенная настройка
2 |
3 | ## Обновление
4 |
5 | ### Обновить библиотеку
6 |
7 | Замените все содержимое файла `library.gs` на новое CtrlA и CtrlV, которое берется [здесь](https://github.com/Chimildic/goofy/blob/main/library.js) или [здесь](https://script.google.com/d/1DnC4H7yjqPV2unMZ_nmB-1bDSJT9wQUJ7Wq-ijF4Nc7Fl3qnbT0FkPSr/edit?usp=sharing) CtrlA и CtrlC и сохраните файл.
8 |
9 | ### Обновить параметры
10 |
11 | 1. Измените требуемый параметр в файле `config.gs` (актуальный список находится [здесь](https://github.com/Chimildic/goofy/blob/main/config.js))
12 | 2. Запустите в редакторе функцию `setProperties`
13 |
14 | ### Обновить права доступа
15 |
16 | При расширении функций библиотеки, могут потребоваться дополнительные права доступа. Например, на загрузку изображения или доступ к любимым трекам. При такой необходимости, в [списке изменений](/changelog.md) очередного обновления будут указания на обновление прав доступа.
17 |
18 | 1. Вставьте следующую функцию и запустите в редакторе один раз. После можно ее удалить.
19 | ```js
20 | function resetAuth(){
21 | Auth.reset();
22 | }
23 | ```
24 | 2. Нажмите в верхнем меню редактора `начать развертывание` - `пробные развертывания`
25 | 3. Скопируйте ссылку из открывшегося окна и перейдите по ней в новой вкладке
26 | 4. Нажмите `Authorize` и подтвердите новые права доступа
27 |
28 | ## Часовой пояс
29 |
30 | Чтобы работать с датами в вашем часовом поясе, выполните следующие шаги:
31 |
32 | 1. Найдите свой часовой пояс [здесь](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). Нужно значение из второй колонки. Например, `Europe/Moscow`.
33 | 2. Зайдите в настройки проекта (меню слева), поставьте галку напротив `Показывать файл манифеста appsscript.json в редакторе`.
34 | 3. Перейдите в файл `appsscript.json` и поменяйте значение у `timeZone` на ваше, сохраните изменение.
35 | 4. Вернитесь в настройки проекта и отключите видимость файла `appsscript.json`.
36 |
37 | ## Настройка last.fm
38 |
39 | Необходимо для модуля [Last.fm](/reference/lastfm). Если не используется, выполнять не нужно.
40 |
41 | 1. Подключите Spotify к Last.fm [здесь](https://www.last.fm/settings/applications)
42 | 2. Создайте точку входа [здесь](https://www.last.fm/api/account/create). Заполните название и описание произвольно. Остальное пропустить, оставить пустым.
43 | 3. Полученный `API key` присвоить параметру `LASTFM_API_KEY`. Также укажите свой логин в `LASTFM_LOGIN`. И задайте `true` для `ON_LASTFM_RECENT_TRACKS`.
44 | 4. Запустить в редакторе выполнение функции `setProperties`.
45 |
46 | 
47 |
48 | ## Настройка musicmatch
49 |
50 | Необходимо для функции [detectLanguage](/reference/filter?id=detectlanguage). Если не используется, выполнять не нужно.
51 |
52 | 1. Зайдите на сайт [developer.musixmatch.com](https://developer.musixmatch.com/signup), чтобы зарегистрироваться и получить API ключ. Телефон и адрес заполните произвольно (123 и World, к примеру). Почта реальная, попросит подтвердить.
53 | 2. После подтверждения почты зайдите в [дэшборд приложений](https://developer.musixmatch.com/admin/applications). Скопируйте ключ.
54 | 3. Вставьте в параметры из файла `config` и запустите функцию `setProperties`.
55 |
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Chimildic
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
Конструктор плейлистов Spotify. Сбор треков, фильтр, обновление по событиям. Бесплатно.
8 |
9 | ## Возможности
10 |
11 | - отслеживает историю прослушиваний
12 | - повторяет функции Smarter Playlists и Playlist Miner
13 | - имеет большой набор фильтров
14 | - собирает рекомендации и новые релизы, включая Every Noise
15 | - импортирует FM-радио и Last.fm
16 | - поддерживает расширенный поиск
17 | - позволяет динамически менять очередь треков
18 | - работает по расписанию и событиям Tasker
19 |
--------------------------------------------------------------------------------