Skip to content

Commit e9357bb

Browse files
authored
Merge pull request #153 from codingpot/feat-add-youtube-cmd-to-dbctl
feat add youtube cmd to dbctl
2 parents 0cf4cd3 + 3a85ca9 commit e9357bb

File tree

5 files changed

+50287
-49915
lines changed

5 files changed

+50287
-49915
lines changed

dbctl/cmd/youtube.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"github.com/codingpot/pr12er/dbctl/internal/transform"
8+
"github.com/codingpot/pr12er/server/pkg/pr12er"
9+
log "github.com/sirupsen/logrus"
10+
"github.com/spf13/cobra"
11+
"github.com/spf13/viper"
12+
"golang.org/x/time/rate"
13+
"google.golang.org/protobuf/encoding/prototext"
14+
)
15+
16+
const envNameYouTubeLink = "YOUTUBE_LINK"
17+
18+
// youtubeCmd represents the youtube command.
19+
var youtubeCmd = &cobra.Command{
20+
Use: "youtube",
21+
Short: "Generate a single GenMapping to STDOUT",
22+
Long: `You can run this file and redirect to the mapping file.
23+
24+
For example,
25+
go run main.go youtube --youtube-link https://www.youtube.com/watch?v=rtuJqQDWmIA >> ../server/internal/data/mapping_table.pbtxt
26+
`,
27+
RunE: func(cmd *cobra.Command, args []string) error {
28+
apiKey := viper.GetString(envNameYouTubeAPIKey)
29+
youTubeLink := viper.GetString(envNameYouTubeLink)
30+
cx := viper.GetString(envNameCx)
31+
32+
log.WithFields(log.Fields{
33+
envNameYouTubeAPIKey: apiKey,
34+
envNameYouTubeLink: youTubeLink,
35+
envNameCx: cx,
36+
}).Info("binding variables")
37+
38+
rateLimiter := rate.NewLimiter(rate.Every(time.Second), 1)
39+
40+
youtubeID, err := transform.ExtractYouTubeID(youTubeLink)
41+
if err != nil {
42+
return err
43+
}
44+
45+
client, err := fetcherClient(apiKey)
46+
if err != nil {
47+
return err
48+
}
49+
50+
// Get video metadata.
51+
youTubeVideos, err := client.FetchMultiYouTubeVideoByIDs([]string{youtubeID})
52+
if err != nil {
53+
return err
54+
}
55+
56+
if len(youTubeVideos) != 1 {
57+
return fmt.Errorf("expected one video but got %v", youTubeVideos)
58+
}
59+
60+
vid := youTubeVideos[0]
61+
62+
// Get PR ID from video.
63+
prID, err := transform.ExtractPRID(vid.GetVideoTitle())
64+
if err != nil {
65+
return err
66+
}
67+
68+
var paperIDs []string
69+
70+
// Get paper IDs.
71+
if cx == "" {
72+
paperIDs, err = transform.ExtractPaperIDs(vid.GetVideoTitle())
73+
} else {
74+
paperIDs, err = transform.
75+
ExtractPaperIDsViaProgrammableSearch(
76+
vid.GetVideoTitle(),
77+
cx,
78+
apiKey,
79+
rateLimiter)
80+
}
81+
if err != nil {
82+
return err
83+
}
84+
85+
data := &pr12er.MappingTable{
86+
Rows: []*pr12er.MappingTableRow{
87+
{
88+
PrId: prID,
89+
PaperArxivIds: paperIDs,
90+
YoutubeVideoId: vid.GetVideoId(),
91+
},
92+
},
93+
}
94+
95+
bs, err := prototext.MarshalOptions{
96+
Multiline: true,
97+
Indent: " ",
98+
}.Marshal(data)
99+
if err != nil {
100+
return err
101+
}
102+
103+
// Print to stdout
104+
// nolint:forbidigo
105+
fmt.Print(string(bs))
106+
107+
return nil
108+
},
109+
}
110+
111+
func init() {
112+
rootCmd.AddCommand(youtubeCmd)
113+
youtubeCmd.Flags().String("youtube-api-key", "", "YouTube API Key (required)")
114+
_ = viper.BindPFlag(envNameYouTubeAPIKey, youtubeCmd.Flag("youtube-api-key"))
115+
youtubeCmd.Flags().String("youtube-link", "", "YouTube Link to fetch")
116+
_ = viper.BindPFlag(envNameYouTubeLink, youtubeCmd.Flag("youtube-link"))
117+
youtubeCmd.Flags().String("cx", "", "Search Engine ID from https://programmablesearchengine.google.com/ If this is not set, it will use googlesearch (free)")
118+
_ = viper.BindPFlag(envNameCx, youtubeCmd.Flag("cx"))
119+
}

dbctl/internal/transform/transform.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package transform
44
import (
55
"context"
66
"fmt"
7+
"net/url"
78
"regexp"
89
"strconv"
910
"strings"
@@ -171,3 +172,45 @@ func ExtractPRID(title string) (int32, error) {
171172
}
172173
return int32(atoi), nil
173174
}
175+
176+
type ErrNotYouTubeLink struct {
177+
url string
178+
}
179+
180+
func (e ErrNotYouTubeLink) Error() string {
181+
return fmt.Sprintf("no valid YouTubeID is found in %s", e.url)
182+
}
183+
184+
// ExtractYouTubeID extracts videoID from YouTube link
185+
//
186+
// For example,
187+
//
188+
// https://www.youtube.com/watch?v=rtuJqQDWmIA => rtuJqQDWmIA
189+
// https://youtube.com/watch?v=rtuJqQDWmIA => rtuJqQDWmIA
190+
// https://youtu.be/rtuJqQDWmIA => rtuJqQDWmIA
191+
func ExtractYouTubeID(link string) (string, error) {
192+
parse, err := url.Parse(link)
193+
if err != nil {
194+
return "", err
195+
}
196+
197+
errNotYouTubeLink := ErrNotYouTubeLink{link}
198+
199+
if strings.Contains(parse.Hostname(), "youtube") {
200+
youtubeID := parse.Query().Get("v")
201+
if youtubeID == "" {
202+
return "", errNotYouTubeLink
203+
}
204+
return youtubeID, nil
205+
}
206+
207+
if strings.Contains(parse.Hostname(), "youtu.be") {
208+
youtubeID := strings.TrimPrefix(parse.Path, "/")
209+
if youtubeID == "" {
210+
return "", errNotYouTubeLink
211+
}
212+
return youtubeID, nil
213+
}
214+
215+
return "", errNotYouTubeLink
216+
}

dbctl/internal/transform/transform_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,59 @@ func TestExtractArxivIDFromURL(t *testing.T) {
203203
})
204204
}
205205
}
206+
207+
func TestExtractYouTubeID(t *testing.T) {
208+
type args struct {
209+
link string
210+
}
211+
tests := []struct {
212+
name string
213+
args args
214+
want string
215+
wantErr bool
216+
}{
217+
{
218+
name: "https://www.youtube.com/watch?v=rtuJqQDWmIA => rtuJqQDWmIA",
219+
args: args{
220+
link: "https://www.youtube.com/watch?v=rtuJqQDWmIA",
221+
},
222+
want: "rtuJqQDWmIA",
223+
wantErr: false,
224+
},
225+
{
226+
name: "https://youtube.com/watch?v=rtuJqQDWmIA => rtuJqQDWmIA",
227+
args: args{
228+
link: "https://youtube.com/watch?v=rtuJqQDWmIA",
229+
},
230+
want: "rtuJqQDWmIA",
231+
wantErr: false,
232+
},
233+
{
234+
name: "https://youtu.be/rtuJqQDWmIA => rtuJqQDWmIA",
235+
args: args{
236+
link: "https://youtu.be/rtuJqQDWmIA",
237+
},
238+
want: "rtuJqQDWmIA",
239+
wantErr: false,
240+
},
241+
{
242+
name: "invalid url returns an error",
243+
args: args{
244+
link: "www.gooogle.com",
245+
},
246+
want: "",
247+
wantErr: true,
248+
},
249+
}
250+
for _, tt := range tests {
251+
t.Run(tt.name, func(t *testing.T) {
252+
got, err := ExtractYouTubeID(tt.args.link)
253+
if tt.wantErr {
254+
assert.Error(t, err)
255+
} else {
256+
assert.NoError(t, err)
257+
assert.Equal(t, tt.want, got)
258+
}
259+
})
260+
}
261+
}

0 commit comments

Comments
 (0)