go get "google.golang.org/api/youtube/v3"
go get "golang.org/x/oauth2"
youtube api와 OAuth 2.0 클라이언트 인증을 사용하기 위해서 외부 패키지를 다운로드해주세요.
package main
import (
"context"
"fmt"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/option"
"google.golang.org/api/youtube/v3"
"io/ioutil"
"log"
"net/http"
)
func main() {
//Read json file (json 파일 읽어옴)
jsonKey, err := ioutil.ReadFile("client.json")
if err != nil {
log.Fatalf("Failed to read JSON file: %v", err)
}
// Set up the OAuth 2.0 configuration (OAuth 2.0 구성 환경 설정)
config, err := google.ConfigFromJSON(jsonKey, youtube.YoutubeReadonlyScope)
if err != nil {
log.Fatalf("Unable to parse client secret file: %v", err)
}
// Create a new OAuth 2.0 client (새로운 OAuth 2.0 클라이언트를 생성 합니다.)
client := getClient(config)
// Set up the YouTube API client using the authenticated client (인증된 클라이언트를 사용하여 유튜브 API 클라이언트를 설정)
service, err := youtube.NewService(context.Background(), option.WithHTTPClient(client))
if err != nil {
log.Fatalf("Unable to create YouTube service: %v", err)
}
// Retrieve the playlist ID for the private playlist you want to access (접근을 원하는 비공개 재생목록 ID)
playlistID := "자신의 계정에 비공개 재생목록의 ID를 넣어주세요"
// Retrieve the playlist items from the private playlist (비공개 재생목록으로부터 재생목록 아이템들을 불러옵니다.)
playlistItemsCall := service.PlaylistItems.List([]string{"snippet"}).
PlaylistId(playlistID). // 재생목록 ID 설정
MaxResults(50) // Adjust the maximum number of results as per your requirements(가져올 재생목록 item 최대값 설정)
playlistItemsResponse, err := playlistItemsCall.Do() // "youtube.playlistItems.list" 호출 실행.
if err != nil {
log.Fatalf("Unable to retrieve playlist items: %v", err)
}
// Process and display the playlist items //가져온 재생목록 아이템들을 제목과 ID 출력
for _, item := range playlistItemsResponse.Items {
title := item.Snippet.Title
videoID := item.Snippet.ResourceId.VideoId
fmt.Printf("Title: %s, Video ID: %s\n", title, videoID)
}
}
// getClient retrieves a valid OAuth 2.0 client.(유효한 OAuth 2.0 클라이언트를 불러옵니다.)
// 토큰트로 *http.Client를 생성하고 반환
func getClient(config *oauth2.Config) *http.Client {
// Retrieve a token, if it exists, or prompts the user to authenticate.
token := getTokenFromWeb(config)
return config.Client(context.Background(), token)
}
// getTokenFromWeb uses the provided OAuth 2.0 Config to request a Token. (제공된 Auth 2.0 구성을 사용해서 토큰을 요청)
// It returns the retrieved Token.(유효한 토큰을 반환)
func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
fmt.Printf("Go to the following link in your browser, then type the "+
"authorization code: \n%v\n", authURL)
//토큰 입력
var code string
if _, err := fmt.Scan(&code); err != nil {
log.Fatalf("Unable to read authorization code: %v", err)
}
token, err := config.Exchange(context.Background(), code)
if err != nil {
log.Fatalf("Unable to retrieve token from web: %v", err)
}
return token
}
- json 파일과 권한 범위를 가지고 *oauth2.Config 구조체를 구성하고 반환함(토큰을 받기 위한 정보가 있음) jsonKey : 3편에서 다운로드한 json파일 youtube.YoutubeReadonlyScope : 접근 가능한 권한 범위(읽기 전용으로 설정함)
- 인증코드를 얻기 위한 동의 페이지 url을 반환해 준다. (사용자 인증 후 인증코드 반환) "state-token" : csrf 방지를 위한 매개변수 oauth2.AccessTypeOffline : 토큰에 관한 설정에 관한 매개변수 (처음에만 인증하고 재인증 필요 없이 리프레쉬 토큰 재발급 가능)
youtube.NewService(ctx, option.WithAPIKey(*apiKey)) 함수를 사용해서 생성 합니다.
첫 번째 인자는 Context이고, 두 번째 인자는 구글 API 클라이언트에 대한 clientOption 입니다. WithAPIKey를 사용하여 apiKey에 대한 clientOption을 반환해 줍니다.
reqPlaylist := service.PlaylistItems.List([]string{"snippet"}). // Set up snippet option
PlaylistId(*playlistID). //Set up playlistID
MaxResults(50) //Set up Max Result
Now we have the missing piece we needed to explain the design of the append built-in function. The signature of append is different from our custom Append function above. Schematically, it's like this:
이제 우리는 append 내장 함수의 디자인을 설명하기 위해서 우리가 필요했던 잃어버린 조각(위에서 부족한 설명에 대한 답)을 가지고 있다. append의 특징은 위에서 본 우리의 커스텀 Append 함수(https://go.dev/doc/effective_go#slices)와 다르다. 개략적으로 이것은 다음과 같다:
func append(slice []T, elements ...T) []T
where T is a placeholder for any given type. You can't actually write a function in Go where the type T is determined by the caller. That's why append is built in: it needs support from the compiler.
여기에서 T는 어떤 주어진 타입에 대한 placeholder(제네릭, 일시적으로 값을 대체하거나 나중에 채워질 것으로 예상되는 위치 )이다. Go에서는 타입 T가 호출자에 의해 결정되는 함수를 작성할 수 없다. 이것이 append가 내장함수인 이유이고: 컴파일러의 지원이 필요하다.
위의 문장에서 보충설명 : 제네릭 T를 사용할 때 타입을 명시해줘야 된다. 안그러면 오류가 난다. 하지만 위에 append 함수는 호출하는 쪽에서 타입을 정할수 있다. 컴파일러의 지원덕분에 가능. 내장함수 append는a := append([]int{}, 1, 2, 3)처럼 호출하는 쪽에서 []int로 정할수 있다. 하지만 커스텀 함수에서 제네릭은 이렇게 타입을 명시해줘야함. 여기서는 any로 해줌.
func Print[T any](a T) {
fmt.Println(a)
}
What append does is append the elements to the end of the slice and return the result. The result needs to be returned because, as with our hand-written Append, the underlying array may change. This simple example
append가 하는 것은 요소들을 slice끝에 삽입하고, 결과를 리턴하는 것이다. 우리의 직접 작성된 Append(https://go.dev/doc/effective_go#slices) 와 같이 실제 배열(슬라이스 안에 배열을 가리키는 포인터)은 바뀔수도 있기 때문에, 결과는 반환되어야할 필요가 있다. 이 간단한 예제는
x := []int{1,2,3}
x = append(x, 4, 5, 6)
fmt.Println(x)
prints [1 2 3 4 5 6]. So append works a little like Printf, collecting an arbitrary number of arguments.
[1 2 3 4 5 6]을 출력한다. 그래서 append는 약간 Printf처럼 동작하고, 임의의 인자들의 수를 수집한다.
But what if we wanted to do what our Append does and append a slice to a slice? Easy: use ... at the call site, just as we did in the call to Output above. This snippet produces identical output to the one above.
그러나 우리의 Append가 하는 것을 하길 원하고, 슬라이스에 슬라이스를 추가하고 싶다면 어떻게 할까? 쉽다: call site(함수 인자에)에 ...을 사용해라, 위에서(https://go.dev/doc/effective_go#Printing) Output(std.Output 함수) 호출에서 했던 것처럼. 아래 snippet(작은 예제)은 위 예제와 동일한 출력을 만든다.
위의 문장에서 보충설명: 위의 custom 함수 Append와 같은 기능을 하고 슬라이스에 슬라이스를 추가하고 싶을 때를 설명하고 있다. 위에 func Println(v ...interface{}) { std.Output(2, fmt.Sprintln(v...)) // Output takes parameters (int, string) } 함수에서 했던 것처럼 ...을 사용 하라는 의미인것으로 보인다.
x := []int{1,2,3}
y := []int{4,5,6}
x = append(x, y...)
fmt.Println(x)
Without that ..., it wouldn't compile because the types would be wrong; y is not of type int.
...없이, 위의 예제는 타입들이 틀리기 때문에 컴파일되지 않을 것이다; 예시 : y는 int 타입이 아니다.
The expression ~string means the set of all types whose underlying type is string. ~string 표현식은 실제 타입이 string인 모든 타입의 집합을 의미한다.
This includes the type string itself as well as all types declared with definitions such as type MyString string. 이것은 type MyString string와 같은 정의와 함께 선언된 모든 타입들 뿐만 아니라 string타입 자신을 포함합니다.
해석 : type키워드를 사용해서 string을 MyString(Named Type)으로 선언하였다. string과 MyString은 서로 다르다.
하지만 ~string을 사용하면 string자신뿐만 아니라 type MyString string로 선언된 타입까지 사용할 수 있다는 뜻이다.
제네릭으로 예제를 만들어보면,
Integer 인터페이스에 int앞에 tilde(~)를 안 넣어주니 오류가 나온다. MyInt는 Integer를 구현하지 않았다고 나오고 가로 안에 아마 Integer안에 int 앞에 ~를 잃어 버렸다고 한다. Integer 인터페이스 안에 int 앞에 tilde(~) 를 넣어주면 올바르게 작동할 것이다. 이렇게 Integer안에 정의한 자료형들을 underlying type도 허용하고 싶으면 앞에 tilde(~)를 넣어주면 된다.