├── .github └── funding.yml ├── addons ├── readme.md ├── Запуск с телефона │ ├── helper.js │ ├── launch.html │ └── readme.md ├── История прослушиваний от приложений │ ├── history-manager.js │ └── readme.md ├── Новые релизы по жанрам │ └── release.js └── Треки с FM-радио │ └── radio.js ├── config.js ├── docs ├── .nojekyll ├── _navbar.md ├── _sidebar.md ├── addon.md ├── audiolist.md ├── best-practices.md ├── changelog.md ├── config.md ├── details.md ├── errors.md ├── first-playlist.md ├── guide.md ├── img │ ├── DaysRel.png │ ├── SmarterPlaylistsExample1.png │ ├── acousticness.png │ ├── cheerio.png │ ├── cmdp-fast-comment.gif │ ├── cmdp-fast-copy.gif │ ├── cmdp-font.gif │ ├── cmdp-move-code.gif │ ├── cmdp-rename-f2.gif │ ├── cmdp-vertical-select-no-mouse.gif │ ├── cmdp-vertical-select.gif │ ├── cross-add-library.png │ ├── cross-project-id.png │ ├── danceability.png │ ├── debuger.png │ ├── energy.png │ ├── example-log.png │ ├── favicon.svg │ ├── find-genres.png │ ├── fp-debug.gif │ ├── fp-triggers-open.gif │ ├── general-property.gif │ ├── goofy-command-audiolist.jpg │ ├── ic_mode_edit_black_24dp.svg │ ├── install-permission-request.png │ ├── install-run-setProperties.png │ ├── install-step-account.png │ ├── install-step-callback-link.png │ ├── install-step-client-id2.png │ ├── install-step-copy.png │ ├── install-step-create-app.png │ ├── install-step-dashboard-redirect.png │ ├── install-step-dashboard-term.png │ ├── install-step-grant-permissions.png │ ├── install-step-grant-spotify.png │ ├── install-step-link.png │ ├── install-step-warning.png │ ├── install-step-webapp.png │ ├── instrumentalness.png │ ├── lastfm_account_api3.png │ ├── liveness.png │ ├── logo.svg │ ├── loudness.png │ ├── new-deploy-audiolist.png │ ├── remote-control.png │ ├── run-func.gif │ ├── sp-kf.png │ ├── sp-mc.png │ ├── speechiness.png │ ├── spotify-share.gif │ ├── tempo.png │ ├── update-deploy-audiolist.png │ └── valence.png ├── index.html ├── install.md ├── migrate2.md ├── overview.md ├── reference │ ├── _sidebar.md │ ├── cache.md │ ├── clerk.md │ ├── combiner.md │ ├── desc.md │ ├── filter.md │ ├── index.md │ ├── lastfm.md │ ├── library.md │ ├── order.md │ ├── player.md │ ├── playlist.md │ ├── recenttracks.md │ ├── search.md │ ├── selector.md │ └── source.md ├── script │ ├── docsify-fix-pageload-scroll.js │ └── docsify-sidebar-collapse.js ├── style │ ├── base-theme.css │ ├── custom.css │ ├── dark-theme.css │ └── light-theme.css ├── template.md └── tuning.md ├── library.js ├── license └── readme.md /.github/funding.yml: -------------------------------------------------------------------------------- 1 | custom: ["https://yoomoney.ru/to/410014208620686"] 2 | -------------------------------------------------------------------------------- /addons/readme.md: -------------------------------------------------------------------------------- 1 | Актуальное описание этих и других аддонов находится на [форуме в категории избранное](https://github.com/Chimildic/goofy/discussions/categories/избранное). -------------------------------------------------------------------------------- /addons/Запуск с телефона/helper.js: -------------------------------------------------------------------------------- 1 | const Helper = (function () { 2 | const FILENAME = 'LaunchState.json'; 3 | 4 | return { 5 | parseId: parseId, 6 | readState: readState, 7 | updateState: updateState, 8 | }; 9 | 10 | function parseId(string) { 11 | let pattern = '[open.spotify.com|spotify]+[\/|:](track|playlist|album|artist|show|episode|concert|user)[\/|:]([^?/#& ]+)'; 12 | let [fullMatch, type, id] = string.match(new RegExp(pattern, 'i')) || []; 13 | return id; 14 | } 15 | 16 | function readState(id) { 17 | return readAllStates()[id] || {}; 18 | } 19 | 20 | function updateState(id, value = {}) { 21 | let states = readAllStates(); 22 | states[id] = value; 23 | Cache.write(FILENAME, states); 24 | } 25 | 26 | function readAllStates(){ 27 | let states = Cache.read(FILENAME); 28 | return Array.isArray(states) ? {} : states; 29 | } 30 | 31 | })(); -------------------------------------------------------------------------------- /addons/Запуск с телефона/launch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
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 | ![Управлять с телефона](img/remote-control.png ':size=50%') 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 | ![Новое развертывание](/img/new-deploy-audiolist.png ':size=60%') 13 | 14 | 3. Скопируйте `идентификатор развертывания` и удобным вам способом перенесите на устройство где установлен Audiolist. 15 | 16 | !> Не передавайте идентификатор другим пользователям. Они смогут запускать ваши функции. 17 | 18 | ### Audiolist 19 | 20 | 1. Создайте программу в Audiolist и добавьте из общего списка команду `функция goofy`. 21 | 2. При заполнении формы команды оставьте поля с переменными пустыми. Укажите `идентификатор развертывания`, полученный при настройке goofy. В качестве _имени функции_ для примера используем `Audiolist.hello` 22 | 23 | ![Команда "Функция goofy"](/img/goofy-command-audiolist.jpg ':size=60%') 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 | ![Новое развертывание](/img/update-deploy-audiolist.png ':size=60%') 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 | ![Пример создания плейлиста в Smarter Playlists](/img/SmarterPlaylistsExample1.png) 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 | ![Поделиться](img/spotify-share.gif) 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 | ![Выполнить функцию](/img/run-func.gif) 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 | ![Триггеры текущего проекта](/img/fp-triggers-open.gif ':size=60%') 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 показывает высокую уверенность в этом. ![Распределение значений acousticness](/img/acousticness.png) 73 | | `danceability` | 0.0 - 1.0 | Оценивает насколько трек подходит для танца, основываясь на его темпе, стабильности ритма, битах и общих закономерностях показателей. Менее танцевальны треки близкие к 0.0 и более к 1.0 ![Распределение значений danceability](/img/danceability.png) 74 | | `energy` | 0.0 - 1.0 | Оценка интенсивности и активности трека. Как правило, энергичные треки кажутся быстрыми, громкими и шумными. Например, треки жанра дэт-метал. Расчет основывается на динамическом диапазоне, громкости, тембре, скорости нарастания и общей энтропии. Менее энергичны треки близкие к 0.0 и более к 1.0 ![Распределение значений energy](/img/energy.png) 75 | | `instrumentalness` | 0.0 - 1.0 | Оценка наличия вокала. Например, рэп или разговорный трек явно имеет вокал. Чем ближе значение к 1.0 тем более вероятно, что трек не содержит вокала. Значение выше 0.5 понимается как инструментальный трек, но вероятность выше при приближении к единице. ![Распределение значений instrumentalness](/img/instrumentalness.png) 76 | | `liveness` | 0.0 - 1.0 | Оценка присутствия аудитории в записи трека или live-трек. Значения выше 0.8 отражают высокую вероятность этого. ![Распределение значений liveness](/img/liveness.png) 77 | | `loudness` | -60 до 0 | Общая громкость в децибелах. Значение громкости усредняется по всему треку. Полезно при сравнении относительность громкости треков. Как правило, диапазон от -60 до 0 дБ. ![Распределение значений loudness](/img/loudness.png) 78 | | `speechiness` | 0.0 - 1.0 | Оценка количества произнесенных слов в треке. Значение близкое к 1.0 характеризует дорожку как ток-шоу, подкаст или аудио-книгу. Треки со значением выше 0.66 вероятно полностью состоят из слов. От 0.33 до 0.66 могут содержать как речь, так и музыку. Ниже 0.33 для музыки и треков без речи. ![Распределение значений speechiness](/img/speechiness.png) 79 | | `valence` | 0.0 - 1.0 | Оценка позитивности трека. Высокое значение говорит о более счастливом, веселом настроении. Низкое значение характерно для треков с грустным, депрессивным настроем. ![Распределение значений valence](/img/valence.png) 80 | | `tempo` | 30 - 210 | Общий темп трека из расчета ударов в минуту (BPM). ![Распределение значений tempo](/img/tempo.png) 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 | 2 | 3 | Layer 1 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /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 | 2 | 3 | Layer 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /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 | ![Создание приложения](/img/install-step-create-app.png ':size=40%') 12 | 13 | 3. Перейдите к [библиотеке в Apps Script](https://script.google.com/d/1DnC4H7yjqPV2unMZ_nmB-1bDSJT9wQUJ7Wq-ijF4Nc7Fl3qnbT0FkPSr/edit?usp=sharing). Войдите в Google аккаунт, если потребуется. 14 | 15 | 4. Выберите слева в раскрывающемся меню `Общие сведения`. 16 | 17 | ![Открыть меню](/img/general-property.gif ':size=60%') 18 | 19 | На открывшейся странице, справа `Создать копию`. Откроется копия, созданная на вашем аккаунте. Переименуйте, если нужно (нажать на имя вверху страницы). 20 | 21 | ![Создать копию](/img/install-step-copy.png) 22 | 23 | 5. Перейдите в файл `config.gs`. Вставьте `CLIENT_ID` и `CLIENT_SECRET` вместо слов `вашеЗначение`. Значения брать в созданном приложении Spotify на шаге 2 (кнопка `Settings`). 24 | 25 | ![Client ID и Client Secret](/img/install-step-client-id2.png) 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 | ![run setProperties](/img/install-run-setProperties.png) 36 | 37 | Увидите всплывающее сообщение с необходимость предоставить права доступа. Согласитесь на выдачу. 38 | 39 | ![запрос прав](/img/install-permission-request.png ':size=50%') 40 | 41 | Выберите Google аккаунт, на котором создали копию библиотеки. 42 | 43 | ![Выбор аккаунта](/img/install-step-account.png) 44 | 45 | Нажмите `Дополнительные настройки`, затем `Перейти на страницу "Копия Goofy (Ver. 1)"` 46 | 47 | ![Выбор аккаунта](/img/install-step-warning.png ':size=50%') 48 | 49 | Нажмите кнопку `Разрешить` внизу окна. 50 | 51 | ![Выбор аккаунта](/img/install-step-grant-permissions.png) 52 | 53 | 8. Окно закроется. Выберите `Начать развертывание` - `Пробные развертывания` 54 | 55 | ![Развернуть веб-приложение](/img/install-step-webapp.png ':size=40%') 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. Сбор треков, фильтр, обновление по событиям. Бесплатно.
4 | 5 |
Выполните установку и создайте свой первый плейлист.
6 |
7 | 8 |

Для андроид есть приложение Audiolist

9 | 10 |
Чат
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 показывает высокую уверенность в этом. ![Распределение значений acousticness](../img/acousticness.png) 40 | | `danceability` | 0.0 - 1.0 | Оценивает насколько трек подходит для танца, основываясь на его темпе, стабильности ритма, битах и общих закономерностях показателей. Менее танцевальны треки близкие к 0.0 и более к 1.0 ![Распределение значений danceability](../img/danceability.png) 41 | | `energy` | 0.0 - 1.0 | Оценка интенсивности и активности трека. Как правило, энергичные треки кажутся быстрыми, громкими и шумными. Например, треки жанра дэт-метал. Расчет основывается на динамическом диапазоне, громкости, тембре, скорости нарастания и общей энтропии. Менее энергичны треки близкие к 0.0 и более к 1.0 ![Распределение значений energy](../img/energy.png) 42 | | `instrumentalness` | 0.0 - 1.0 | Оценка наличия вокала. Например, рэп или разговорный трек явно имеет вокал. Чем ближе значение к 1.0 тем более вероятно, что трек не содержит вокала. Значение выше 0.5 понимается как инструментальный трек, но вероятность выше при приближении к единице. ![Распределение значений instrumentalness](../img/instrumentalness.png) 43 | | `liveness` | 0.0 - 1.0 | Оценка присутствия аудитории в записи трека или live-трек. Значения выше 0.8 отражают высокую вероятность этого. ![Распределение значений liveness](../img/liveness.png) 44 | | `loudness` | -60 до 0 | Общая громкость в децибелах. Значение громкости усредняется по всему треку. Полезно при сравнении относительность громкости треков. Как правило, диапазон от -60 до 0 дБ. ![Распределение значений loudness](../img/loudness.png) 45 | | `speechiness` | 0.0 - 1.0 | Оценка количества произнесенных слов в треке. Значение близкое к 1.0 характеризует дорожку как ток-шоу, подкаст или аудио-книгу. Треки со значением выше 0.66 вероятно полностью состоят из слов. От 0.33 до 0.66 могут содержать как речь, так и музыку. Ниже 0.33 для музыки и треков без речи. ![Распределение значений speechiness](../img/speechiness.png) 46 | | `valence` | 0.0 - 1.0 | Оценка позитивности трека. Высокое значение говорит о более счастливом, веселом настроении. Низкое значение характерно для треков с грустным, депрессивным настроем. ![Распределение значений valence](../img/valence.png) 47 | | `tempo` | 30 - 210 | Общий темп трека из расчета ударов в минуту (BPM). ![Распределение значений tempo](../img/tempo.png) 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 | ![Lastfm account api](/img/lastfm_account_api3.png) 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. Сбор треков, фильтр, обновление по событиям. Бесплатно.

3 |

Выполните установку и создайте свой первый плейлист.

4 | 5 |

Андроид приложение Audiolist

6 | 7 |

Чат

8 | 9 | ## Возможности 10 | 11 | - отслеживает историю прослушиваний 12 | - повторяет функции Smarter Playlists и Playlist Miner 13 | - имеет большой набор фильтров 14 | - собирает рекомендации и новые релизы, включая Every Noise 15 | - импортирует FM-радио и Last.fm 16 | - поддерживает расширенный поиск 17 | - позволяет динамически менять очередь треков 18 | - работает по расписанию и событиям Tasker 19 | --------------------------------------------------------------------------------