Skip to content

Commit

Permalink
Merge branch 'main' into feat/addConfig
Browse files Browse the repository at this point in the history
  • Loading branch information
liulinboyi committed Jul 21, 2023
2 parents c10c8ec + 9350996 commit bbd1651
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 20 deletions.
17 changes: 11 additions & 6 deletions examples/music/src/chatifyActionsSchema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// This is a schema for writing programs that control a Spotify music player

export type Track = { name: string }
export type Playlist = unknown;

export type Track = { name: string };
export type TrackList = Track[];

export type FavoritesTerm =
Expand All @@ -27,7 +29,7 @@ export type PlayTracksOptions = {
count?: number;
// index of first track to play; default 0
offset?: number;
}
};

export type FilterTracksArgs = {
// a filter string, which has the following structure (written as a grammar)
Expand All @@ -38,8 +40,9 @@ export type FilterTracksArgs = {
filter: string;
// keep the tracks that do not match, instead of the tracks that match; default false
negate?: boolean;
}
};

export type PlaybackAction = "pause" | "next" | "previous" | "shuffle";

export type API = {
// show now playing
Expand All @@ -48,10 +51,12 @@ export type API = {
getPlaylistTracks(name: string): TrackList;
// Return a list of the user's favorite tracks
getFavorites(options?: GetFavoritesOptions): TrackList;
// Pause playing
pause(): void;
// control playback
controlPlayback(action: PlaybackAction): void;
// List all playlists
listPlaylists(): void;
// Get playlist
getPlaylist(name: string): Playlist;
// Delete playlist 'name'
deletePlaylist(name: string): void;
// Set volume
Expand All @@ -62,7 +67,7 @@ export type API = {
// Return the last track list shown to the user
getLastTrackList(): TrackList;
// play some or all items from the input list
play(trackList: TrackList, options?: PlayTracksOptions): void;
play(item: TrackList | Playlist, options?: PlayTracksOptions): void;
// apply a filter to match tracks; result is the tracks that match the filter
filterTracks(trackList: TrackList, args: FilterTracksArgs): TrackList;
// print a list of tracks
Expand Down
88 changes: 86 additions & 2 deletions examples/music/src/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@ export async function getPlaybackState(service: SpotifyService) {
export async function play(
service: SpotifyService,
deviceId: string,
uris?: string[]
uris?: string[],
contextUri?: string
) {
const config = {
headers: {
Expand All @@ -219,6 +220,8 @@ export async function play(
const smallTrack: SpotifyApi.PlayParameterObject = {};
if (uris) {
smallTrack.uris = uris;
} else if (contextUri) {
smallTrack.context_uri = contextUri;
}
const playUrl = getUrlWithParams("https://api.spotify.com/v1/me/player/play", { device_id: deviceId });
try {
Expand Down Expand Up @@ -288,6 +291,88 @@ export async function pause(service: SpotifyService, deviceId: string) {
}
}

export async function next(
service: SpotifyService,
deviceId: string,
) {
const config = {
headers: {
Authorization: `Bearer ${service.retrieveUser().token}`,
},
};
try {
const spotifyResult = await axios.post(
`https://api.spotify.com/v1/me/player/next?device_id=${deviceId}`,
{},
config
);

return spotifyResult.data;
} catch (e) {
if (e instanceof axios.AxiosError) {
console.log(e.message);
} else {
throw e;
}
}
return undefined;
}

export async function previous(
service: SpotifyService,
deviceId: string,
) {
const config = {
headers: {
Authorization: `Bearer ${service.retrieveUser().token}`,
},
};
try {
const spotifyResult = await axios.post(
`https://api.spotify.com/v1/me/player/previous?device_id=${deviceId}`,
{},
config
);

return spotifyResult.data;
} catch (e) {
if (e instanceof axios.AxiosError) {
console.log(e.message);
} else {
throw e;
}
}
return undefined;
}

export async function shuffle(
service: SpotifyService,
deviceId: string,
newShuffleState: boolean
) {
const config = {
headers: {
Authorization: `Bearer ${service.retrieveUser().token}`,
},
};
try {
const spotifyResult = await axios.put(
`https://api.spotify.com/v1/me/player/shuffle?state=${newShuffleState}&device_id=${deviceId}`,
{},
config
);

return spotifyResult.data;
} catch (e) {
if (e instanceof axios.AxiosError) {
console.log(e.message);
} else {
throw e;
}
}
return undefined;
}

export async function getPlaylists(service: SpotifyService) {
const config = {
headers: {
Expand Down Expand Up @@ -380,7 +465,6 @@ export async function createPlaylist(
{ name, public: false, description },
config
);

const playlistResponse =
spotifyResult.data as SpotifyApi.CreatePlaylistResponse;
const addTracksResult = await axios.post(
Expand Down
3 changes: 3 additions & 0 deletions examples/music/src/input.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ get my top ten tracks since January
get my favorite 100 tracks from the last two months and show only the ones by Bach
make it loud
get my favorite 80 tracks from the last 8 months and create one playlist named class8 containing the classical tracks and another playlist containing the blues tracks
toggle shuffle on and skip to the next track
go back to the last song
play my playlist class8
69 changes: 57 additions & 12 deletions examples/music/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
getKRecent,
limitMax,
getTopK,
pause,
// getArtist,
createPlaylist,
deletePlaylist,
Expand All @@ -38,6 +37,12 @@ import {
getPlaybackState,
getPlaylistTracks,
} from "./endpoints";
import {
pauseHandler,
nextHandler,
previousHandler,
shuffleHandler
} from "./playback";
import { SpotifyService, User } from "./service";

dotenv.config({ path: path.join(__dirname, "../../../.env") });
Expand All @@ -55,7 +60,7 @@ const keys = {
clientSecret: process.env.SPOTIFY_APP_CLISEC,
};

interface IClientContext {
export interface IClientContext {
service: SpotifyService;
deviceId?: string;
user: User;
Expand All @@ -80,6 +85,18 @@ function printTrackNames(
}
}

async function printPlaylist(playlist: SpotifyApi.PlaylistObjectSimplified, context: IClientContext) {
console.log(chalk.cyanBright(`Starting playlist --> ${playlist.name}`));
console.log(chalk.cyanBright(`--------------------------------------------`));
const tracks = await getPlaylistTracks(context.service, playlist.id);
const playlistTotalTracks = playlist.tracks.total;
console.log(chalk.cyan(`First ${tracks?.items.length} out of ${playlistTotalTracks} songs in list`));
tracks?.items.forEach((track, i) => {
console.log(chalk.cyan(` ${i < 99 ? i < 9 ? " " : " " : ""}${i + 1} - ${track.track?.name}`));
})
console.log(chalk.cyanBright(`--------------------------------------------`));
}

function uniqueTracks(tracks: SpotifyApi.TrackObjectFull[]) {
const map = new Map<string, SpotifyApi.TrackObjectFull>();
for (const track of tracks) {
Expand Down Expand Up @@ -129,13 +146,17 @@ function chalkPlan(plan: Program) {

function localParser(userPrompt: string) {
userPrompt = userPrompt.trim();
if (userPrompt === "play" || userPrompt === "pause") {
if (userPrompt === "play" || userPrompt === "pause" || userPrompt === "next" || userPrompt === "previous" || userPrompt === "shuffle") {
console.log(chalk.green("Instance parsed locally:"));
let localParseResult = userPrompt;
if (userPrompt !== "play") {
localParseResult = "controlPlayback";
}
return JSON.stringify({
"@steps": [
{
"@func": userPrompt,
"@args": [],
"@func": localParseResult,
"@args": [userPrompt !== "play" ? userPrompt : ""],
},
],
});
Expand Down Expand Up @@ -378,23 +399,30 @@ async function handleCall(
args: unknown[],
clientContext: IClientContext
): Promise<unknown> {
let result: SpotifyApi.TrackObjectFull[] | undefined = undefined;
let result: SpotifyApi.TrackObjectFull[] | SpotifyApi.PlaylistObjectSimplified | undefined = undefined;
switch (func) {
case "getLastTrackList": {
if (clientContext) {
result = clientContext.lastTrackList;
}
break;
}
case "pause": {
if (clientContext.deviceId) {
await pause(clientContext.service, clientContext.deviceId);
}
case "controlPlayback" : {
const action = args[0] as string;

const actionHandlers: { [key:string] : (clientContext: IClientContext) => Promise<void> } = {
pause : pauseHandler,
next : nextHandler,
previous : previousHandler,
shuffle: shuffleHandler
};

await actionHandlers[action](clientContext);
break;
}
case "play": {
const input = args[0] as SpotifyApi.TrackObjectFull[];
if (input && input.length > 0) {
const input = args[0] as SpotifyApi.TrackObjectFull[] | SpotifyApi.PlaylistObjectSimplified;
if (Array.isArray(input) && input && input.length > 0) {
let count = 1;
let offset = 0;
let options = args[1] as PlayTracksOptions;
Expand All @@ -420,6 +448,12 @@ async function handleCall(
if (clientContext.deviceId) {
await play(clientContext.service, clientContext.deviceId, uris);
}
} else if(!Array.isArray(input) && input && input.type === 'playlist') {
const uri = input.uri;
if (clientContext.deviceId) {
await printPlaylist(input, clientContext);
await play(clientContext.service, clientContext.deviceId, undefined, uri);
}
} else if (clientContext.deviceId) {
await play(clientContext.service, clientContext.deviceId);
}
Expand Down Expand Up @@ -522,6 +556,17 @@ async function handleCall(
}
break;
}
case "getPlaylist" : {
const playlistName = args[0] as string;
const playlists = await getPlaylists(clientContext.service);
const playlist = playlists?.items.find(
(playlist) => {
return playlist.name.toLowerCase().includes(playlistName.toLowerCase());
}
);
result = playlist
break;
}
case "getPlaylistTracks": {
const playlistName = args[0] as string;
const playlists = await getPlaylists(clientContext.service);
Expand Down
34 changes: 34 additions & 0 deletions examples/music/src/playback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { IClientContext } from "./main";
import chalk from "chalk";
import { pause, next, previous, shuffle, getPlaybackState} from "./endpoints";

export async function pauseHandler(clientContext: IClientContext) {
if (clientContext.deviceId) {
await pause(clientContext.service, clientContext.deviceId);
}
}

export async function nextHandler(clientContext: IClientContext) {
if (clientContext.deviceId) {
await next(clientContext.service, clientContext.deviceId);
}
}

export async function previousHandler(clientContext: IClientContext) {
if (clientContext.deviceId) {
await previous(clientContext.service, clientContext.deviceId);
}
}

export async function shuffleHandler(clientContext: IClientContext) {
const playbackState = await getPlaybackState(clientContext.service);

if (playbackState) {
const oldShuffleState = playbackState.shuffle_state;

if (clientContext.deviceId) {
console.log(chalk.cyanBright(`Toggling shuffle ${oldShuffleState ? chalk.redBright("off") : chalk.greenBright("on") }`));
await shuffle(clientContext.service, clientContext.deviceId, !oldShuffleState);
}
}
}

0 comments on commit bbd1651

Please sign in to comment.