iT邦幫忙

2023 iThome 鐵人賽

DAY 29
0
SideProject30

Golang LineBot X GoogleDrive:LINE有各種限制!? 那就丟上Drive吧!系列 第 29

Day29 Drive Service - 用Google Drive API取資料建立Carousel

  • 分享至 

  • xImage
  •  

我們昨天完成了整個組合Carousel的部分,今天來把資料來源換成從Google Drive API取得。

  1. 首先,我們先看一下需要哪些資料

    type NewFolderBubbleParam struct {
    	Type          string // 資料夾類型
    	Name          string // 資料夾名稱
    	Path          string // 資料夾路徑
    	ID            string // folderID
    	InsideFolderM map[string]string // 子資料夾,[id]name
    	FileM         map[string]string // 檔案,[id]name
    }
    
  2. 那我們就從下往上一一來做,先處理FileM,我們到adapter的drive.go中,寫一個ListFilesByID()的function,讓他根據folderID回傳下面所有檔案的Map。

    透過Q(fmt.Sprintf("'%s' in parents and mimeType != 'application/vnd.google-apps.folder'", folderID),我們去到指定資料夾並把檔案內容過濾掉屬於”資料夾”類型的檔案,剩下的就是我們要的檔案了,再透過Fields("files(id, name)")取出對應的id跟檔案名稱。

    // internal\adapter\google\drive.go
    func (d *GoogleDrive) ListFilesByID(folderID string) (map[string]string, error) {
    	nameM := make(map[string]string)
    	r, err := d.Service.Files.List().Q(fmt.Sprintf("'%s' in parents and mimeType != 'application/vnd.google-apps.folder'", folderID)).
    		Fields("files(id, name)").Do()
    	if err != nil {
    		log.Printf("Unable to retrieve files: %v", err)
    		return nil, err
    	}
    
    	fmt.Println("Files:")
    	if len(r.Files) == 0 {
    		fmt.Println("No files found.")
    	} else {
    		for _, i := range r.Files {
    			nameM[i.Id] = i.Name
    			fmt.Printf("%s (%s)\n", i.Name, i.Id)
    		}
    	}
    
    	fmt.Println("nameM 共:", len(nameM), "個檔案")
    	return nameM, nil
    }
    
  3. 接著處理InsideFolderM ,跟上面幾乎一樣的邏輯,只是這邊我們透過Q(fmt.Sprintf("'%s' in parents and mimeType='application/vnd.google-apps.folder'", folderID)),讓他只去取得類型是”資料夾”的內容。

    // internal\adapter\google\drive.go
    func (d *GoogleDrive) ListFolderByID(folderID string) (map[string]string, error) {
    	nameM := make(map[string]string)
    	r, err := d.Service.Files.List().Q(fmt.Sprintf("'%s' in parents and mimeType='application/vnd.google-apps.folder'", folderID)).
    		Fields("files(id, name)").Do()
    	if err != nil {
    		log.Printf("Unable to retrieve folders: %v", err)
    		return nil, err
    	}
    
    	fmt.Println("Folders:")
    	if len(r.Files) == 0 {
    		fmt.Println("No folders found.")
    	} else {
    		for _, i := range r.Files {
    			nameM[i.Id] = i.Name
    			fmt.Printf("%s (%s)\n", i.Name, i.Id)
    		}
    	}
    
    	fmt.Println("nameM 共:", len(nameM), "個資料夾")
    	return nameM, nil
    }
    
  4. ID的部分是從外部帶去做button的,所以我們接著來做PathPath比較麻煩,因為Google Drive提供的API並沒有直接查詢資料夾所在路徑的功能,但有可以取得上層資料夾ID的功能,所以我們可以自己透過遞迴來實現。

    // internal\adapter\google\drive.go
    func (d *GoogleDrive) FindFolderPathByID(folderID string) (string, error) {
    	return d.findFolderPathByIDRecursively(folderID, "")
    }
    
    func (d *GoogleDrive) findFolderPathByIDRecursively(folderID string, currentPath string) (string, error) {
    	r, err := d.Service.Files.Get(folderID).Fields("id, name, parents,shared").Do()
    	if err != nil {
    		log.Printf("Unable to retrieve folder information: %v", err)
    		return "", err
    	}
    
    	currentPath = r.Name + "/" + currentPath
    
    	if len(r.Parents) > 0 {
    		return d.findFolderPathByIDRecursively(r.Parents[0], currentPath)
    	}
    
    	if r.Shared {
    		currentPath = "與我共用/" + currentPath
    	}
    
    	return currentPath, nil
    }
    

    我們透過在Fields中取parents來拿到上級資料夾的id,如果還拿的到上一層的parents我們就不斷遞迴。比較特別的是在遞迴完後,我們會透過r.Shared來判斷當前資料是否共用,因為在”與我共用”的那一塊他預設是直接在跟目錄下的,所以我們在開頭加上"與我共用/",這樣在看路徑的時候會比較清楚到底對應在Google Drive的哪裡。

  5. 接著NameType的部分也是從外部帶入的,所以我們可以來寫Service了。在開始之前為了比較好定義Type,我們先到Domain去自定義Type的類型。

    // internal\domain\drive\flex.go
    type FolderType int
    
    const (
    	PersonalFolder FolderType = iota
    	SharedFolder
    	OtherFolder
    )
    
  6. 然後回到Service層,我們寫一個ListFolderCarousel()會從外部傳入domainDrive.FolderType,並且透過switch case判斷完類型之後,決定要用ListMyDriveFolders()還是ListSharedFolders(),也從這邊決定類型要顯示的名稱。剩下的就是拿到最外層資料夾的folderList後,迴圈呼叫對應的adapter function,然後將值放進去NewFolderBubbleParam就好哩。

    // internal\app\service\drive\drive_service.go
    func (dr *GoogleDriveService) ListFolderCarousel(ctx context.Context, lineID string, folderType domainDrive.FolderType) (*domainDrive.FolderCarousel, error) {
    	dToken, err := dr.driveServiceDynamodb.GetGoogleOAuthToken(lineID)
    	if err != nil {
    		log.Println(err)
    		return nil, err
    	}
    	tok := oauth2.Token{
    		AccessToken:  dToken.AccessToken,
    		TokenType:    dToken.TokenType,
    		RefreshToken: dToken.RefreshToken,
    		Expiry:       dToken.Expiry,
    	}
    	d, err := dr.driveServiceGoogleOA.NewGoogleDrive(ctx, &tok)
    	if err != nil {
    		log.Println(err)
    		return nil, err
    	}
    
    	var folderList map[string]string
    	var folderTypeString string
    	switch folderType {
    	case domainDrive.PersonalFolder:
    		folderList, err = d.ListMyDriveFolders()
    		folderTypeString = "我的雲端硬碟"
    	case domainDrive.SharedFolder:
    		folderList, err = d.ListSharedFolders()
    		folderTypeString = "與我共用"
    	default:
    		return nil, errors.New("unsupported folder type")
    	}
    	if err != nil {
    		return nil, err
    	}
    
    	var params domainDrive.NewFolderCarouselParam
    
    	for folderID, name := range folderList {
    		path, err := d.FindFolderPathByID(folderID)
    		if err != nil {
    			log.Println(err)
    			return nil, err
    		}
    		insideFolderM, err := d.ListFolderByID(folderID)
    		if err != nil {
    			log.Println(err)
    			return nil, err
    		}
    		fileM, err := d.ListFilesByID(folderID)
    		if err != nil {
    			log.Println(err)
    			return nil, err
    		}
    		param := domainDrive.NewFolderBubbleParam{
    			Type:          folderTypeString,
    			Name:          name,
    			Path:          path,
    			ID:            folderID,
    			InsideFolderM: insideFolderM,
    			FileM:         fileM,
    		}
    		params.BubbleParams = append(params.BubbleParams, param)
    	}
    
    	carousel := domainDrive.NewFolderCarousel(params)
    
    	return &carousel, err
    }
    
  7. 最後我們回到callback,補上兩個條件,並透過domainDrive.PersonalFolderdomainDrive.SharedFolder來決定要回傳哪種Carousel。

    // internal\router\api\v1\callback.go
    if message.Text == "mydrive" {
    	lineID := event.Source.UserID
    	res, err := app.DriveService.ListFolderCarousel(ctx, lineID, domainDrive.PersonalFolder)
    	if err != nil {
    		log.Println(err)
    		return
    	}
    	if _, err := app.LineBotClient.ReplyMessage(
    		event.ReplyToken,
    		linebot.NewFlexMessage("測試Flex Carousel", res.CarouselContainer),
    	).Do(); err != nil {
    		log.Println(err)
    		return
    	}
    }
    if message.Text == "shared" {
    	lineID := event.Source.UserID
    	res, err := app.DriveService.ListFolderCarousel(ctx, lineID, domainDrive.SharedFolder)
    	if err != nil {
    		log.Println(err)
    		return
    	}
    	if _, err := app.LineBotClient.ReplyMessage(
    		event.ReplyToken,
    		linebot.NewFlexMessage("測試Flex Carousel", res.CarouselContainer),
    	).Do(); err != nil {
    		log.Println(err)
    		return
    	}
    }
    
  8. 回到LineBot測試一下,可以看到能夠正確取得我們GoogleDrive上的資料,並且組合出對應的Carousel了。那我們今天就先寫到這裡,明天是最後一天,補上button的功能應該就可以告一段落了~

    https://ithelp.ithome.com.tw/upload/images/20231014/20115990hC3rUfTLhP.png
    https://ithelp.ithome.com.tw/upload/images/20231014/20115990RpApZ0Km4v.png


上一篇
Day28 用Go動態組合Flex Message 02
下一篇
Day30 為Button補上對應的功能
系列文
Golang LineBot X GoogleDrive:LINE有各種限制!? 那就丟上Drive吧!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言