使用Go和AWS Lambda管理Spotify库

Spotify公开了一个健壮的API,可用于管理您(或其他人)音乐库并使用它进行各种有趣的操作。 一旦设置了OAuth 2身份验证并准备就绪,应用程序就可以代表用户与Spotify API进行持续交互,以执行任何操作,从获取有关用户的基本信息到寻找当前播放的歌曲中的位置。

在本文中,我们将逐步设置在AWS Lambda上托管的Go应用程序,该应用程序将执行一些涉及Discover Weekly播放列表的简单库管理。 具体来说,我们的lambda每周运行一次,并从“发现每周”播放列表中添加一周中所有“喜欢”的歌曲,并将它们添加到另一个播放列表中。 我一直手动进行此操作已经有一段时间了,因为我想跟踪我从“发现周刊”中翻阅过的所有歌曲,而不保留整个播放列表的副本。 这是对Spotify API的很好的介绍,因为它足够简单,可以快速上手,但是它确实使用了一些不同的Spotify端点,并且做了一些逻辑来正确放置东西。

该示例的所有源代码都托管在GitHub上,并且可在Apache-2.0许可下获得。

设置一个Spotify应用程序

首先要做的是登录到Spotify开发人员控制台并通过选择“创建应用程序”注册一个新应用程序:

按照他们的指示注册新应用程序,完成后,您将进入应用程序主页,您将在其中看到您的客户ID和客户密码:

我们稍后将在应用程序中使用它们。 最后一步-使用Spotify应用程序进行身份验证要求应用程序在登录后知道应该将用户重定向到的位置。稍后,我们将在本地运行此操作以生成令牌。 因此,从您的应用程序页面,转到“ Edit Settings ,然后添加http://localhost:8080/callback作为重定向URI:

这就是设置应用程序!

几个环境变量

如果要遵循的存储库代码,您要做的第一件事就是在存储库的根目录下建立一个.env文件(或者只是导出它们,但以后再使用):

 SPOTIFY_ID= 
SPOTIFY_SECRET=
BUCKET=
TARGET_PLAYLIST=
TOKEN_FILE=
REGION=

我们将继续填写这些值,但现在可以填写一些值:

  • 创建您的Spotify应用程序后,可以使用在步骤1中获得的凭据来填充SPOTIFY_IDSPOTIFY_SECRET
  • 也可以将TOKEN_FILE填写为所需的任何值-这将是存储在S3中的令牌文件的名称。
  • TARGET_PLAYLIST是您要在其中添加歌曲的Spotify帐户中播放列表的名称-如果尚不存在,则创建它。

为Spotify生成OAuth 2令牌

现在,我们需要生成一个OAuth2令牌,我们的应用将使用该令牌对Spotify进行身份验证。 Spotify有几种不同的身份验证方法,其授权指南中对此进行了详细介绍。 由于此应用程序将访问和修改实际的用户数据(而不仅仅是访问通常可访问的数据,例如艺术家的信息),因此我们需要实际登录并授予我们的应用程序对我们自己的Spotify帐户的访问权限。 为此,我们将使用Spotify文档中的授权代码流。 在此过程中,将用户定向到网页,在此他们可以登录其Spotify帐户并授予对应用程序的访问权限。 然后,我们可以使用在步骤1中创建的客户端ID和客户端密钥,使用用户登录时生成的授权代码向Spotify请求令牌。获得令牌后,Go OAuth 2库将自动处理刷新令牌过期(Spotify令牌在一小时后过期)。

让我们看一些代码,看看实际情况。 一些令牌生成代码是从这个很棒的Go Spotify客户端修改而来的,该客户端在整个项目中都用于与Spotify API进行交互。

为了简洁起见,这些代码示例被截断了-查看完整的源代码回购。

在仓库的cmd / get-token / main.go中:

  1. 我们首先从Go Spotify客户端库中设置了一个auth变量,该变量将为我们提供一个用于指导用户登录的URL。
  2. 然后,我们启动一个HTTP服务器,该服务器将在用户登录其帐户后处理重定向。
  3. 最后,我们打印出供用户登录的URL,然后等待他们登录。

auth变量值得进一步说明:传递给NewAuthenticator的各种“作用域”指定了我们的应用程序将能够访问的内容。 它们还用于在授予访问权限之前向用户提醒我们的应用程序正在尝试访问的内容。 名称是不言自明的,每个Spotify API端点都有一定的范围要求。 他们的API参考包含有关其他端点的更多信息。 此NewAuthenticator使用环境变量SPOTIFY_IDSPOTIFY_SECRET进行身份验证,因此在运行此代码之前,请确保已设置了这些变量。

 ch = make(chan *spotify.Client) 
auth = spotify.NewAuthenticator(
redirectURI,
spotify.ScopeUserLibraryRead,
spotify.ScopePlaylistModifyPrivate,
spotify.ScopePlaylistReadPrivate
)
 // Start server with a callback handler to complete the authentication. http.HandleFunc("/callback", completeAuth) 
go http.ListenAndServe(":8080", nil)

url := auth.AuthURL(state)
log.Println("Please log in to Spotify by visiting the following page in your browser:", url)
 // Wait for the authentication. client := <-ch 

一旦用户(在本例中为我们)登录,他们将被重定向到我们的小型HTTP服务器,并调用处理程序来处理授权:

 func completeAuth(w http.ResponseWriter, r *http.Request) { 
  //将授权代码交换为令牌。 
tok,err:= auth.Token(state,r)
如果err!= nil {
http.Error(w,“无法获取令牌”,http.StatusForbidden)
log.Fatal(错误)
}
 如果st:= r.FormValue(“ state”);  st!=状态{ 
http.NotFound(w,r)
log.Fatalf(“状态不匹配:%s!=%s \ n”,st,状态)
}
  //使用令牌获取经过身份验证的客户端。 
客户端:= auth.NewClient(tok)
fmt.Fprintf(w,“登录完成!”)
ch <-&client
  btys,err:= json.Marshal(tok) 
如果err!= nil {
log.Fatalf(“无法封送令牌:%v”,错误)
}
  // Save the token to S3 for later use. 
if _, err := s3.Upload(&s3manager.UploadInput{
Bucket: aws.String(config.Bucket),
Key: aws.String(config.TokenFile),
Body: bytes.NewReader(btys),
}); err != nil {
log.Fatalf("could not write token to s3: %v", err)
}
}

该处理程序将请求正文中的授权代码交换为有效令牌。 然后它将令牌保存到S3,以便我们稍后可以在Lambda函数中使用它,并在通道上将客户端发送回去,以便可以用于通知用户他们已登录:

  //等待身份验证。 
客户:= <-ch
  //获取用户。 
用户,错误:= client.CurrentUser()
如果err!= nil {
log.Fatal(错误)
}
  log.Println(“用户名:”,用户名。) 
log.Println(“令牌已保存到s3”)

甜! 现在,我们已经为自己的Spotify帐户生成了OAuth2令牌,该令牌可被我们的应用程序用来修改其在帐户中有权访问的任何内容。

配置AWS基础架构

如果您还没有AWS账户,则可以免费注册一个。 与新帐户相关联的有12个月的免费套餐,但也有一个正在进行的免费套餐。 此示例中使用的资源极少,并且在常规的免费套餐内,因此,如果您已经拥有一个帐户,则无需花任何钱就可以运行。

创建一个S3存储桶

我们将创建的第一件事是容纳我们的OAuth2令牌的S3存储桶。

  • 从“ Services选项卡转到S3主页。
  • 选择Create bucket
  • 为您的存储桶选择一个名称,然后在提示屏幕中进行操作-默认设置对此适用。
  • 将存储桶的名称和您的区域添加到.env创建的.env文件中。

为Lambda函数创建IAM角色

接下来,我们将创建一个IAM角色,我们的Lambda将使用该角色访问S3。

  • 从“ Services选项卡转到IAM主页。
  • 选择左侧的Roles
  • 选择Create Role
  • 选择Lambda作为将使用此角色的服务。
  • 在下一个屏幕上,搜索Lambda并选择AWSLambdaExecute策略并继续。 此策略将授予访问权限以在S3中获取和放置对象-正是我们检索和写入令牌所需要的。
  • 在最后一页上,将您的角色lambda_execution并创建它。 您可以根据需要为角色选择其他名称,只需确保相应地更新Makefile。

在本地添加您的AWS凭证

如果您已经设置了与AWS进行交互的机器,则可以跳过此步骤。 由于我们在本地生成令牌并将其保存到S3,因此我们必须能够对我们的AWS账户进行身份验证。 这是通过访问密钥完成的。 如果您使用Root登录到您的AWS账户,我强烈建议您自己创建一个单独的用户,并将其用于所有账户活动。 该用户将需要访问权限以将对象写入您创建的S3存储桶。

  • 在IAM界面中,选择“ Users然后选择您作为登录用户。
  • 转到“ Security Credentials选项卡,然后选择“ Create access key
  • 将密钥存储在〜/ .aws / credentials文件中:
 [default] 
aws_access_key_id =
aws_secret_access_key =

并创建一个〜/ .aws / config文件来处理默认值:

 [default] 
output = json
region = us-east-1

AWS Lambda函数

好了,现在我们已经完成了所有的身份验证和基础架构工作,我们可以进入有趣的部分!

我们的Lambda函数将执行以下操作:

  • 从S3下载我们的令牌文件。
  • 使用令牌创建一个新的Spotify客户端。
  • 检查令牌是否已刷新,如果已刷新,则将更新的令牌保存回S3。
  • 获取该用户的所有播放列表的列表。
  • 从“发现每周”播放列表中获取所有歌曲。
  • 检查“发现每周”中的任何歌曲是否已保存在用户库中。
  • 将这些歌曲中的任何一个保存到环境变量指定的另一个播放列表中。

让我们通过一些代码示例来逐一介绍。

从S3下载我们的令牌文件

首先要做的是从S3获取令牌。 在这里,我们使用最初创建令牌时使用的相同TokenFile来从S3存储桶中获取相同的令牌,并将其解组为新的OAuth2令牌。

  //从S3下载令牌文件。 
buff:=&aws.WriteAtBuffer {}
如果_,err = s3dl.Download(buff,&s3.GetObjectInput {
值区:aws.String(config.Bucket),
密钥:aws.String(config.TokenFile),
}); err!= nil {
log.Fatalf(“无法从S3下载令牌文件:%v”,错误)
}
 托克:= new(oauth2.Token) 
如果err:= json.Unmarshal(buff.Bytes(),tok); err!= nil {
log.Fatalf(“无法解组令牌:%v”,错误)
}

使用令牌创建一个新的Spotify客户端并检查是否已刷新

我们首先将令牌传递到空白的Authenticator以创建新客户端。 然后,我们在该客户端上获取令牌,并检查它是否与我们存储在S3上的访问令牌相同。 由于Spotify令牌会在一个小时后过期,因此很有可能是其他令牌,因此我们将其保存到S3并覆盖那里的令牌。

  //使用oauth2令牌创建一个Spotify身份验证器。 
//如果令牌已过期,则oauth2软件包将自动刷新
//因此,将新令牌与旧令牌进行对照,以查看是否应对其进行更新。
客户端:= spotify.NewAuthenticator(“”)。NewClient(tok)
  newToken,err:= client.Token() 
如果err!= nil {
log.Fatalf(“无法从客户端获取令牌:%v”,错误)
}
 如果newToken.AccessToken!= tok.AccessToken { 
log.Println(“刷新令牌,将其保存”)
  btys,err:= json.Marshal(newToken) 
如果err!= nil {
log.Fatalf(“无法封送令牌:%v”,错误)
}
如果_,错误:= s3ul.Upload(&s3manager.UploadInput {
值区:aws.String(config.Bucket),
密钥:aws.String(config.TokenFile),
正文:bytes.NewReader(btys),
}); err!= nil {
log.Fatalf(“无法将令牌写入s3:%v”,错误)
}
}

获取用户的所有播放列表的列表

接下来,我们获取库中所有播放列表的列表,然后遍历这些列表以查找“发现每周”播放列表的ID和目标播放列表。

 用户,错误:= client.CurrentUser() 
如果err!= nil {
log.Fatalf(“无法获取用户:%v”,错误)
}
  //检索用户的前50个播放列表。 
播放列表,错误:= client.GetPlaylistsForUser(user.ID)
如果err!= nil {
log.Fatalf(“无法获取播放列表:%v”,错误)
}
  // Vars用于指定“发现每周”播放列表和目标播放列表。 
var(
discoverID spotify.ID
targetID spotify.ID
  //获取“发现每周”播放列表和目标播放列表的ID。 
对于_,p:=范围播放列表。播放列表{
如果p.Name ==“每周发现一次” {
discoverID = p.ID
}
 如果p.Name == config.TargetPlaylist { 
targetID = p.ID
}
}
  //如果未找到其中一个播放列表,请进行纾困。 
如果discoverID ==“” || targetID ==“” {
log.Fatal(“未获取播放列表ID”)
}

从“发现每周”播放列表中获取所有歌曲

我们之前检索的播放列表数组实际上并不包含每个播放列表中的歌曲,因此我们需要使用“播放列表ID”来检索“发现每周”播放列表中的歌曲。

  //从“发现每周”播放列表中获取歌曲。 
//在这里不必担心API的限制,因为它总是包含30首歌曲。
discoverPlaylist,err:= client.GetPlaylist(user.ID,discoverID)
如果err!= nil {
log.Fatalf(“无法获取发现每周播放列表:%v”,错误)
}

检查“发现每周”中的任何歌曲是否保存在用户库中

现在,我们已经在“发现每周”播放列表中找到了所有歌曲,我们可以获取每个歌曲的曲目ID,然后使用Spotify端点告诉我们哪些曲目已保存在用户库中。 对于传入的每个轨道ID,该端点的响应为true / false,其传递顺序与轨道ID传递的顺序相同。

  //对于“发现每周”中的每首歌曲,检查它是否已保存在库中。 
trackIDs:= make([] spotify.ID,0,len(discoverPlaylist.Tracks.Tracks))
  //提取每首歌曲的曲目ID。 
对于_,t:= range discoverPlaylist.Tracks.Tracks {
trackIDs = append(trackIDs,t.Track.SimpleTrack.ID)
}
  //检查它们是否在库中。 
hasTracks,err:= client.UserHasTracks(trackIDs ...)
如果err!= nil {
log.Fatalf(“无法检查库中是否存在轨道:%v”,错误)
}

将其中任何歌曲保存到环境变量指定的另一个播放列表中

最后,我们遍历该清单列表,告诉我们每首歌曲是否在用户库中,并创建包含在“发现每周”播放列表中并保存在用户库中的曲目ID的最终数组。 然后,我们将这些歌曲保存到目标播放列表,仅此而已!

  //检查哪些曲目返回了库,并将其标记为要添加的歌曲。 
addTracks:= make([[spotify.ID,0,len(discoverPlaylist.Tracks.Tracks)))
对于我,b:= range hasTracks {
如果b {
addTracks = append(addTracks,trackIDs [i])
}
}
  //将需要添加的每首歌曲添加到taret播放列表。 
_,err = client.AddTracksToPlaylist(user.ID,targetID,addTracks ...)
如果err!= nil {
log.Fatalf(“无法将曲目添加到播放列表:%v”,错误)
}
  log.Printf(“已成功将%d个曲目添加到播放列表%s \ n”,len(addTracks),config.TargetPlaylist) 

我们已经掌握了所有内容,现在我们只需要将所有内容放在一起即可。 我们将使用Makefile来完成以下几个步骤,下面部分显示。 完整的源代码还包括用于更新Lambda函数的update和update-env命令。

 令牌: 
去运行cmd / get-token / main.go
 上载: 
出口`cat .env | xargs`
env GOOS = linux去构建./cmd/sync-discover
zip sync-discover.zip ./sync-discover
aws lambda创建功能
--region us-east-1 \
-功能名称同步发现\
-内存128
--role arn:aws:iam :: 526123814436:role / lambda_execution \
--runtime go1.x \
--zip文件fileb://sync-discover.zip \
--handler同步发现\
--environment变量=“ {SPOTIFY_ID = $ {SPOTIFY_ID},SPOTIFY_SECRET = $ {SPOTIFY_SECRET},TARGET_PLAYLIST = $ {TARGET_PLAYLIST},BUCKET = $ {BUCKET},TOKEN_FILE = $ {TOKEN_FILE},REGION = $ {REGION}}””
rm -f sync-discover sync-discover.zip

首先要做的是使用HTTP服务器生成令牌。

  • 从存储库的根目录运行make token以启动HTTP服务器。
  • 单击命令行中打印出的链接,然后登录到您的Spotify帐户:
  • 检查您的S3存储桶以验证auth令牌是否正确保存。

然后,将Lambda函数部署到AWS:

  • 运行make upload ,它将首先导出.env文件中的所有环境变量,然后将该函数上传到AWS。
  • 在您的AWS账户中,从Services选项卡中选择Lambda ,并确保您有sync-discover Lambda。
  • 您可以通过选择Lambda控制台右上方的“测试”来测试Lambda,然后触发调用。 如果一切顺利,您应该在执行上取得成功。

最后要做的是设置一个CloudWatch事件,以每周自动调用我们的Lambda函数。

  • 从用于sync-discover Lambda的Lambda控制台中,从“ Add triggers列表中选择“ CloudWatch Events ”。
  • 从下拉列表中选择Create a new rule并为其命名。
  • 对于Rule type选择Schedule expression然后输入0 2 ? * MON * 0 2 ? * MON * 。 这是一个cron表达式,要在每个星期一早上2点UTC运行。 对我来说这是星期天晚上。 “发现每周”播放列表会在星期一清晨的某个时间重置,因此此时间应该不错。 不过请随时为您的时区进行调整。
  • 在那之后,保存规则并确保已启用它,应该行得通!

我希望本教程对Spotify API入门有所帮助。 对于我想在自己的Spotify库中管理的事情,我还有很多想法,很想听听您还有其他想法!