REFACTOR: convert function comments to proper godoc
- also fix indentation in versions.go file
This commit is contained in:
parent
160be3f045
commit
604d5b9ce2
@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
ErrEndOfFeed = errors.New("end of feed")
|
ErrEndOfFeed = errors.New("end of feed")
|
||||||
ErrNotInDB = errors.New("not in database")
|
ErrNotInDB = errors.New("not in database")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p Profile) fill_content(trove *TweetTrove) {
|
func (p Profile) fill_content(trove *TweetTrove) {
|
||||||
|
@ -124,7 +124,7 @@ func TestTweetDetailWithReplies(t *testing.T) {
|
|||||||
1413650853081276421,
|
1413650853081276421,
|
||||||
1413772782358433792,
|
1413772782358433792,
|
||||||
1413773185296650241,
|
1413773185296650241,
|
||||||
}{
|
} {
|
||||||
_, is_ok := tweet_detail.Tweets[id]
|
_, is_ok := tweet_detail.Tweets[id]
|
||||||
assert.True(is_ok)
|
assert.True(is_ok)
|
||||||
}
|
}
|
||||||
@ -135,7 +135,7 @@ func TestTweetDetailWithReplies(t *testing.T) {
|
|||||||
1372116552942764034,
|
1372116552942764034,
|
||||||
1067869346775646208,
|
1067869346775646208,
|
||||||
1304281147074064385,
|
1304281147074064385,
|
||||||
}{
|
} {
|
||||||
_, is_ok := tweet_detail.Users[id]
|
_, is_ok := tweet_detail.Users[id]
|
||||||
assert.True(is_ok)
|
assert.True(is_ok)
|
||||||
}
|
}
|
||||||
@ -174,7 +174,7 @@ func TestTweetDetailWithParents(t *testing.T) {
|
|||||||
1413646595493568516,
|
1413646595493568516,
|
||||||
1413772782358433792,
|
1413772782358433792,
|
||||||
1413773185296650241,
|
1413773185296650241,
|
||||||
}{
|
} {
|
||||||
_, is_ok := tweet_detail.Tweets[id]
|
_, is_ok := tweet_detail.Tweets[id]
|
||||||
assert.True(is_ok)
|
assert.True(is_ok)
|
||||||
}
|
}
|
||||||
|
@ -21,13 +21,11 @@ type DefaultDownloader struct{}
|
|||||||
|
|
||||||
var ErrorDMCA error = errors.New("video is DMCAed, unable to download (HTTP 403 Forbidden)")
|
var ErrorDMCA error = errors.New("video is DMCAed, unable to download (HTTP 403 Forbidden)")
|
||||||
|
|
||||||
/**
|
// Download a file over HTTP and save it.
|
||||||
* Download a file over HTTP and save it.
|
//
|
||||||
*
|
// args:
|
||||||
* args:
|
// - url: the remote file to download
|
||||||
* - url: the remote file to download
|
// - outpath: the path on disk to save it to
|
||||||
* - outpath: the path on disk to save it to
|
|
||||||
*/
|
|
||||||
func (d DefaultDownloader) Curl(url string, outpath string) error {
|
func (d DefaultDownloader) Curl(url string, outpath string) error {
|
||||||
println(url)
|
println(url)
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
@ -81,9 +79,7 @@ func (d DefaultDownloader) Curl(url string, outpath string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Downloads an Image, and if successful, marks it as downloaded in the DB
|
||||||
* Downloads an Image, and if successful, marks it as downloaded in the DB
|
|
||||||
*/
|
|
||||||
func (p Profile) download_tweet_image(img *scraper.Image, downloader MediaDownloader) error {
|
func (p Profile) download_tweet_image(img *scraper.Image, downloader MediaDownloader) error {
|
||||||
outfile := path.Join(p.ProfileDir, "images", img.LocalFilename)
|
outfile := path.Join(p.ProfileDir, "images", img.LocalFilename)
|
||||||
err := downloader.Curl(img.RemoteURL, outfile)
|
err := downloader.Curl(img.RemoteURL, outfile)
|
||||||
@ -94,9 +90,7 @@ func (p Profile) download_tweet_image(img *scraper.Image, downloader MediaDownlo
|
|||||||
return p.SaveImage(*img)
|
return p.SaveImage(*img)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Downloads a Video and its thumbnail, and if successful, marks it as downloaded in the DB
|
||||||
* Downloads a Video and its thumbnail, and if successful, marks it as downloaded in the DB
|
|
||||||
*/
|
|
||||||
func (p Profile) download_tweet_video(v *scraper.Video, downloader MediaDownloader) error {
|
func (p Profile) download_tweet_video(v *scraper.Video, downloader MediaDownloader) error {
|
||||||
// Download the video
|
// Download the video
|
||||||
outfile := path.Join(p.ProfileDir, "videos", v.LocalFilename)
|
outfile := path.Join(p.ProfileDir, "videos", v.LocalFilename)
|
||||||
@ -122,9 +116,7 @@ func (p Profile) download_tweet_video(v *scraper.Video, downloader MediaDownload
|
|||||||
return p.SaveVideo(*v)
|
return p.SaveVideo(*v)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Downloads an URL thumbnail image, and if successful, marks it as downloaded in the DB
|
||||||
* Downloads an URL thumbnail image, and if successful, marks it as downloaded in the DB
|
|
||||||
*/
|
|
||||||
func (p Profile) download_link_thumbnail(url *scraper.Url, downloader MediaDownloader) error {
|
func (p Profile) download_link_thumbnail(url *scraper.Url, downloader MediaDownloader) error {
|
||||||
if url.HasCard && url.HasThumbnail {
|
if url.HasCard && url.HasThumbnail {
|
||||||
outfile := path.Join(p.ProfileDir, "link_preview_images", url.ThumbnailLocalPath)
|
outfile := path.Join(p.ProfileDir, "link_preview_images", url.ThumbnailLocalPath)
|
||||||
@ -137,18 +129,13 @@ func (p Profile) download_link_thumbnail(url *scraper.Url, downloader MediaDownl
|
|||||||
return p.SaveUrl(*url)
|
return p.SaveUrl(*url)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Download a tweet's video and picture content.
|
||||||
* Download a tweet's video and picture content.
|
// Wraps the `DownloadTweetContentWithInjector` method with the default (i.e., real) downloader.
|
||||||
*
|
|
||||||
* Wraps the `DownloadTweetContentWithInjector` method with the default (i.e., real) downloader.
|
|
||||||
*/
|
|
||||||
func (p Profile) DownloadTweetContentFor(t *scraper.Tweet) error {
|
func (p Profile) DownloadTweetContentFor(t *scraper.Tweet) error {
|
||||||
return p.DownloadTweetContentWithInjector(t, DefaultDownloader{})
|
return p.DownloadTweetContentWithInjector(t, DefaultDownloader{})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Enable injecting a custom MediaDownloader (i.e., for testing)
|
||||||
* Enable injecting a custom MediaDownloader (i.e., for testing)
|
|
||||||
*/
|
|
||||||
func (p Profile) DownloadTweetContentWithInjector(t *scraper.Tweet, downloader MediaDownloader) error {
|
func (p Profile) DownloadTweetContentWithInjector(t *scraper.Tweet, downloader MediaDownloader) error {
|
||||||
// Check if content needs to be downloaded; if not, just return
|
// Check if content needs to be downloaded; if not, just return
|
||||||
if !p.CheckTweetContentDownloadNeeded(*t) {
|
if !p.CheckTweetContentDownloadNeeded(*t) {
|
||||||
@ -179,16 +166,12 @@ func (p Profile) DownloadTweetContentWithInjector(t *scraper.Tweet, downloader M
|
|||||||
return p.SaveTweet(*t)
|
return p.SaveTweet(*t)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Download a user's banner and profile images
|
||||||
* Download a user's banner and profile images
|
|
||||||
*/
|
|
||||||
func (p Profile) DownloadUserContentFor(u *scraper.User) error {
|
func (p Profile) DownloadUserContentFor(u *scraper.User) error {
|
||||||
return p.DownloadUserContentWithInjector(u, DefaultDownloader{})
|
return p.DownloadUserContentWithInjector(u, DefaultDownloader{})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Enable injecting a custom MediaDownloader (i.e., for testing)
|
||||||
* Enable injecting a custom MediaDownloader (i.e., for testing)
|
|
||||||
*/
|
|
||||||
func (p Profile) DownloadUserContentWithInjector(u *scraper.User, downloader MediaDownloader) error {
|
func (p Profile) DownloadUserContentWithInjector(u *scraper.User, downloader MediaDownloader) error {
|
||||||
if !p.CheckUserContentDownloadNeeded(*u) {
|
if !p.CheckUserContentDownloadNeeded(*u) {
|
||||||
return nil
|
return nil
|
||||||
@ -226,11 +209,9 @@ func (p Profile) DownloadUserContentWithInjector(u *scraper.User, downloader Med
|
|||||||
return p.SaveUser(u)
|
return p.SaveUser(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Download a User's tiny profile image, if it hasn't been downloaded yet.
|
||||||
* Download a User's tiny profile image, if it hasn't been downloaded yet.
|
// If it has been downloaded, do nothing.
|
||||||
* If it has been downloaded, do nothing.
|
// If this user should have a big profile picture, defer to the regular `DownloadUserContentFor` method.
|
||||||
* If this user should have a big profile picture, defer to the regular `DownloadUserContentFor` method.
|
|
||||||
*/
|
|
||||||
func (p Profile) DownloadUserProfileImageTiny(u *scraper.User) error {
|
func (p Profile) DownloadUserProfileImageTiny(u *scraper.User) error {
|
||||||
if p.IsFollowing(*u) {
|
if p.IsFollowing(*u) {
|
||||||
return p.DownloadUserContentFor(u)
|
return p.DownloadUserContentFor(u)
|
||||||
|
@ -9,9 +9,7 @@ import (
|
|||||||
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
// Some types to spy on a MediaDownloader
|
||||||
* Some types to spy on a MediaDownloader
|
|
||||||
*/
|
|
||||||
type SpyResult struct {
|
type SpyResult struct {
|
||||||
url string
|
url string
|
||||||
outpath string
|
outpath string
|
||||||
@ -63,9 +61,7 @@ func test_all_downloaded(tweet scraper.Tweet, yes_or_no bool, t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Downloading a Tweet's contents should mark the Tweet as downloaded
|
||||||
* Downloading a Tweet's contents should mark the Tweet as downloaded
|
|
||||||
*/
|
|
||||||
func TestDownloadTweetContent(t *testing.T) {
|
func TestDownloadTweetContent(t *testing.T) {
|
||||||
profile_path := "test_profiles/TestMediaQueries"
|
profile_path := "test_profiles/TestMediaQueries"
|
||||||
profile := create_or_load_profile(profile_path)
|
profile := create_or_load_profile(profile_path)
|
||||||
@ -92,9 +88,7 @@ func TestDownloadTweetContent(t *testing.T) {
|
|||||||
test_all_downloaded(new_tweet, true, t)
|
test_all_downloaded(new_tweet, true, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Downloading a User's contents should mark the User as downloaded
|
||||||
* Downloading a User's contents should mark the User as downloaded
|
|
||||||
*/
|
|
||||||
func TestDownloadUserContent(t *testing.T) {
|
func TestDownloadUserContent(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
profile_path := "test_profiles/TestMediaQueries"
|
profile_path := "test_profiles/TestMediaQueries"
|
||||||
@ -134,9 +128,7 @@ func TestDownloadUserContent(t *testing.T) {
|
|||||||
assert.True(new_user.IsContentDownloaded)
|
assert.True(new_user.IsContentDownloaded)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Should download the right stuff if User has no banner image and default profile image
|
||||||
* Should download the right stuff if User has no banner image and default profile image
|
|
||||||
*/
|
|
||||||
func TestDownloadDefaultUserContent(t *testing.T) {
|
func TestDownloadDefaultUserContent(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
profile_path := "test_profiles/TestMediaQueries"
|
profile_path := "test_profiles/TestMediaQueries"
|
||||||
|
@ -6,12 +6,10 @@ import (
|
|||||||
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
// Save an Image
|
||||||
* Save an Image
|
//
|
||||||
*
|
// args:
|
||||||
* args:
|
// - img: the Image to save
|
||||||
* - img: the Image to save
|
|
||||||
*/
|
|
||||||
func (p Profile) SaveImage(img scraper.Image) error {
|
func (p Profile) SaveImage(img scraper.Image) error {
|
||||||
_, err := p.DB.NamedExec(`
|
_, err := p.DB.NamedExec(`
|
||||||
insert into images (id, tweet_id, width, height, remote_url, local_filename, is_downloaded)
|
insert into images (id, tweet_id, width, height, remote_url, local_filename, is_downloaded)
|
||||||
@ -27,12 +25,10 @@ func (p Profile) SaveImage(img scraper.Image) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Save a Video
|
||||||
* Save a Video
|
//
|
||||||
*
|
// args:
|
||||||
* args:
|
// - img: the Video to save
|
||||||
* - img: the Video to save
|
|
||||||
*/
|
|
||||||
func (p Profile) SaveVideo(vid scraper.Video) error {
|
func (p Profile) SaveVideo(vid scraper.Video) error {
|
||||||
_, err := p.DB.NamedExec(`
|
_, err := p.DB.NamedExec(`
|
||||||
insert into videos (id, tweet_id, width, height, remote_url, local_filename, thumbnail_remote_url, thumbnail_local_filename,
|
insert into videos (id, tweet_id, width, height, remote_url, local_filename, thumbnail_remote_url, thumbnail_local_filename,
|
||||||
@ -52,9 +48,7 @@ func (p Profile) SaveVideo(vid scraper.Video) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Save an Url
|
||||||
* Save an Url
|
|
||||||
*/
|
|
||||||
func (p Profile) SaveUrl(url scraper.Url) error {
|
func (p Profile) SaveUrl(url scraper.Url) error {
|
||||||
_, err := p.DB.NamedExec(`
|
_, err := p.DB.NamedExec(`
|
||||||
insert into urls (tweet_id, domain, text, short_text, title, description, creator_id, site_id, thumbnail_width, thumbnail_height,
|
insert into urls (tweet_id, domain, text, short_text, title, description, creator_id, site_id, thumbnail_width, thumbnail_height,
|
||||||
@ -73,9 +67,7 @@ func (p Profile) SaveUrl(url scraper.Url) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Save a Poll
|
||||||
* Save a Poll
|
|
||||||
*/
|
|
||||||
func (p Profile) SavePoll(poll scraper.Poll) error {
|
func (p Profile) SavePoll(poll scraper.Poll) error {
|
||||||
_, err := p.DB.NamedExec(`
|
_, err := p.DB.NamedExec(`
|
||||||
insert into polls (id, tweet_id, num_choices, choice1, choice1_votes, choice2, choice2_votes, choice3, choice3_votes, choice4,
|
insert into polls (id, tweet_id, num_choices, choice1, choice1_votes, choice2, choice2_votes, choice3, choice3_votes, choice4,
|
||||||
@ -97,9 +89,7 @@ func (p Profile) SavePoll(poll scraper.Poll) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Get the list of images for a tweet
|
||||||
* Get the list of images for a tweet
|
|
||||||
*/
|
|
||||||
func (p Profile) GetImagesForTweet(t scraper.Tweet) (imgs []scraper.Image, err error) {
|
func (p Profile) GetImagesForTweet(t scraper.Tweet) (imgs []scraper.Image, err error) {
|
||||||
err = p.DB.Select(&imgs,
|
err = p.DB.Select(&imgs,
|
||||||
"select id, tweet_id, width, height, remote_url, local_filename, is_downloaded from images where tweet_id=?",
|
"select id, tweet_id, width, height, remote_url, local_filename, is_downloaded from images where tweet_id=?",
|
||||||
@ -107,9 +97,7 @@ func (p Profile) GetImagesForTweet(t scraper.Tweet) (imgs []scraper.Image, err e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Get the list of videos for a tweet
|
||||||
* Get the list of videos for a tweet
|
|
||||||
*/
|
|
||||||
func (p Profile) GetVideosForTweet(t scraper.Tweet) (vids []scraper.Video, err error) {
|
func (p Profile) GetVideosForTweet(t scraper.Tweet) (vids []scraper.Video, err error) {
|
||||||
err = p.DB.Select(&vids, `
|
err = p.DB.Select(&vids, `
|
||||||
select id, tweet_id, width, height, remote_url, local_filename, thumbnail_remote_url, thumbnail_local_filename, duration,
|
select id, tweet_id, width, height, remote_url, local_filename, thumbnail_remote_url, thumbnail_local_filename, duration,
|
||||||
@ -120,9 +108,7 @@ func (p Profile) GetVideosForTweet(t scraper.Tweet) (vids []scraper.Video, err e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Get the list of Urls for a Tweet
|
||||||
* Get the list of Urls for a Tweet
|
|
||||||
*/
|
|
||||||
func (p Profile) GetUrlsForTweet(t scraper.Tweet) (urls []scraper.Url, err error) {
|
func (p Profile) GetUrlsForTweet(t scraper.Tweet) (urls []scraper.Url, err error) {
|
||||||
err = p.DB.Select(&urls, `
|
err = p.DB.Select(&urls, `
|
||||||
select tweet_id, domain, text, short_text, title, description, creator_id, site_id, thumbnail_width, thumbnail_height,
|
select tweet_id, domain, text, short_text, title, description, creator_id, site_id, thumbnail_width, thumbnail_height,
|
||||||
@ -134,9 +120,7 @@ func (p Profile) GetUrlsForTweet(t scraper.Tweet) (urls []scraper.Url, err error
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Get the list of Polls for a Tweet
|
||||||
* Get the list of Polls for a Tweet
|
|
||||||
*/
|
|
||||||
func (p Profile) GetPollsForTweet(t scraper.Tweet) (polls []scraper.Poll, err error) {
|
func (p Profile) GetPollsForTweet(t scraper.Tweet) (polls []scraper.Poll, err error) {
|
||||||
err = p.DB.Select(&polls, `
|
err = p.DB.Select(&polls, `
|
||||||
select id, tweet_id, num_choices, choice1, choice1_votes, choice2, choice2_votes, choice3, choice3_votes, choice4, choice4_votes,
|
select id, tweet_id, num_choices, choice1, choice1_votes, choice2, choice2_votes, choice3, choice3_votes, choice4, choice4_votes,
|
||||||
|
@ -12,9 +12,7 @@ import (
|
|||||||
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
// Create an Image, save it, reload it, and make sure it comes back the same
|
||||||
* Create an Image, save it, reload it, and make sure it comes back the same
|
|
||||||
*/
|
|
||||||
func TestSaveAndLoadImage(t *testing.T) {
|
func TestSaveAndLoadImage(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
profile_path := "test_profiles/TestMediaQueries"
|
profile_path := "test_profiles/TestMediaQueries"
|
||||||
@ -47,9 +45,7 @@ func TestSaveAndLoadImage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Change an Image, save the changes, reload it, and check if it comes back the same
|
||||||
* Change an Image, save the changes, reload it, and check if it comes back the same
|
|
||||||
*/
|
|
||||||
func TestModifyImage(t *testing.T) {
|
func TestModifyImage(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
profile_path := "test_profiles/TestMediaQueries"
|
profile_path := "test_profiles/TestMediaQueries"
|
||||||
@ -78,9 +74,7 @@ func TestModifyImage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Create an Video, save it, reload it, and make sure it comes back the same
|
||||||
* Create an Video, save it, reload it, and make sure it comes back the same
|
|
||||||
*/
|
|
||||||
func TestSaveAndLoadVideo(t *testing.T) {
|
func TestSaveAndLoadVideo(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
profile_path := "test_profiles/TestMediaQueries"
|
profile_path := "test_profiles/TestMediaQueries"
|
||||||
@ -116,9 +110,7 @@ func TestSaveAndLoadVideo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Change an Video, save the changes, reload it, and check if it comes back the same
|
||||||
* Change an Video, save the changes, reload it, and check if it comes back the same
|
|
||||||
*/
|
|
||||||
func TestModifyVideo(t *testing.T) {
|
func TestModifyVideo(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
profile_path := "test_profiles/TestMediaQueries"
|
profile_path := "test_profiles/TestMediaQueries"
|
||||||
@ -148,9 +140,7 @@ func TestModifyVideo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Create an Url, save it, reload it, and make sure it comes back the same
|
||||||
* Create an Url, save it, reload it, and make sure it comes back the same
|
|
||||||
*/
|
|
||||||
func TestSaveAndLoadUrl(t *testing.T) {
|
func TestSaveAndLoadUrl(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
profile_path := "test_profiles/TestMediaQueries"
|
profile_path := "test_profiles/TestMediaQueries"
|
||||||
@ -184,9 +174,7 @@ func TestSaveAndLoadUrl(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Change an Url, save the changes, reload it, and check if it comes back the same
|
||||||
* Change an Url, save the changes, reload it, and check if it comes back the same
|
|
||||||
*/
|
|
||||||
func TestModifyUrl(t *testing.T) {
|
func TestModifyUrl(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
profile_path := "test_profiles/TestMediaQueries"
|
profile_path := "test_profiles/TestMediaQueries"
|
||||||
@ -215,9 +203,7 @@ func TestModifyUrl(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Create a Poll, save it, reload it, and make sure it comes back the same
|
||||||
* Create a Poll, save it, reload it, and make sure it comes back the same
|
|
||||||
*/
|
|
||||||
func TestSaveAndLoadPoll(t *testing.T) {
|
func TestSaveAndLoadPoll(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
profile_path := "test_profiles/TestMediaQueries"
|
profile_path := "test_profiles/TestMediaQueries"
|
||||||
@ -249,9 +235,7 @@ func TestSaveAndLoadPoll(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Change an Poll, save the changes, reload it, and check if it comes back the same
|
||||||
* Change an Poll, save the changes, reload it, and check if it comes back the same
|
|
||||||
*/
|
|
||||||
func TestModifyPoll(t *testing.T) {
|
func TestModifyPoll(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
profile_path := "test_profiles/TestMediaQueries"
|
profile_path := "test_profiles/TestMediaQueries"
|
||||||
|
@ -25,16 +25,14 @@ type Profile struct {
|
|||||||
|
|
||||||
var ErrTargetAlreadyExists = fmt.Errorf("Target already exists")
|
var ErrTargetAlreadyExists = fmt.Errorf("Target already exists")
|
||||||
|
|
||||||
/**
|
// Create a new profile in the given location.
|
||||||
* Create a new profile in the given location.
|
// Fails if target location already exists (i.e., is a file or directory).
|
||||||
* Fails if target location already exists (i.e., is a file or directory).
|
//
|
||||||
*
|
// args:
|
||||||
* args:
|
// - target_dir: location to create the new profile directory
|
||||||
* - target_dir: location to create the new profile directory
|
//
|
||||||
*
|
// returns:
|
||||||
* returns:
|
// - the newly created Profile
|
||||||
* - the newly created Profile
|
|
||||||
*/
|
|
||||||
func NewProfile(target_dir string) (Profile, error) {
|
func NewProfile(target_dir string) (Profile, error) {
|
||||||
if file_exists(target_dir) {
|
if file_exists(target_dir) {
|
||||||
return Profile{}, fmt.Errorf("Could not create target %q:\n %w", target_dir, ErrTargetAlreadyExists)
|
return Profile{}, fmt.Errorf("Could not create target %q:\n %w", target_dir, ErrTargetAlreadyExists)
|
||||||
@ -112,15 +110,13 @@ func NewProfile(target_dir string) (Profile, error) {
|
|||||||
return Profile{target_dir, settings, db}, nil
|
return Profile{target_dir, settings, db}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Loads the profile at the given location. Fails if the given directory is not a Profile.
|
||||||
* Loads the profile at the given location. Fails if the given directory is not a Profile.
|
//
|
||||||
*
|
// args:
|
||||||
* args:
|
// - profile_dir: location to check for the profile
|
||||||
* - profile_dir: location to check for the profile
|
//
|
||||||
*
|
// returns:
|
||||||
* returns:
|
// - the loaded Profile
|
||||||
* - the loaded Profile
|
|
||||||
*/
|
|
||||||
func LoadProfile(profile_dir string) (Profile, error) {
|
func LoadProfile(profile_dir string) (Profile, error) {
|
||||||
settings_file := path.Join(profile_dir, "settings.yaml")
|
settings_file := path.Join(profile_dir, "settings.yaml")
|
||||||
sqlite_file := path.Join(profile_dir, "twitter.db")
|
sqlite_file := path.Join(profile_dir, "twitter.db")
|
||||||
|
@ -24,9 +24,7 @@ func file_exists(path string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Should refuse to create a Profile if the target already exists (i.e., is a file or directory).
|
||||||
* Should refuse to create a Profile if the target already exists (i.e., is a file or directory).
|
|
||||||
*/
|
|
||||||
func TestNewProfileInvalidPath(t *testing.T) {
|
func TestNewProfileInvalidPath(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
gibberish_path := "test_profiles/fjlwrefuvaaw23efwm"
|
gibberish_path := "test_profiles/fjlwrefuvaaw23efwm"
|
||||||
@ -43,9 +41,7 @@ func TestNewProfileInvalidPath(t *testing.T) {
|
|||||||
assert.ErrorIs(t, err, persistence.ErrTargetAlreadyExists)
|
assert.ErrorIs(t, err, persistence.ErrTargetAlreadyExists)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Should correctly create a new Profile
|
||||||
* Should correctly create a new Profile
|
|
||||||
*/
|
|
||||||
func TestNewProfile(t *testing.T) {
|
func TestNewProfile(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
@ -90,9 +86,7 @@ func TestNewProfile(t *testing.T) {
|
|||||||
assert.Equal(persistence.ENGINE_DATABASE_VERSION, version)
|
assert.Equal(persistence.ENGINE_DATABASE_VERSION, version)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Should correctly load the Profile
|
||||||
* Should correctly load the Profile
|
|
||||||
*/
|
|
||||||
func TestLoadProfile(t *testing.T) {
|
func TestLoadProfile(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
@ -111,9 +105,7 @@ func TestLoadProfile(t *testing.T) {
|
|||||||
assert.Equal(t, profile_path, profile.ProfileDir)
|
assert.Equal(t, profile_path, profile.ProfileDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Test the ToSnakeCase implementation
|
||||||
* Test the ToSnakeCase implementation
|
|
||||||
*/
|
|
||||||
func TestSnakeCase(t *testing.T) {
|
func TestSnakeCase(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
@ -6,9 +6,7 @@ import (
|
|||||||
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
// Save a Retweet. Do nothing if it already exists, because none of its parameters are modifiable.
|
||||||
* Save a Retweet. Do nothing if it already exists, because none of its parameters are modifiable.
|
|
||||||
*/
|
|
||||||
func (p Profile) SaveRetweet(r scraper.Retweet) error {
|
func (p Profile) SaveRetweet(r scraper.Retweet) error {
|
||||||
_, err := p.DB.NamedExec(`
|
_, err := p.DB.NamedExec(`
|
||||||
insert into retweets (retweet_id, tweet_id, retweeted_by, retweeted_at)
|
insert into retweets (retweet_id, tweet_id, retweeted_by, retweeted_at)
|
||||||
@ -23,9 +21,7 @@ func (p Profile) SaveRetweet(r scraper.Retweet) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Retrieve a Retweet by ID
|
||||||
* Retrieve a Retweet by ID
|
|
||||||
*/
|
|
||||||
func (p Profile) GetRetweetById(id scraper.TweetID) (scraper.Retweet, error) {
|
func (p Profile) GetRetweetById(id scraper.TweetID) (scraper.Retweet, error) {
|
||||||
var r scraper.Retweet
|
var r scraper.Retweet
|
||||||
err := p.DB.Get(&r, `
|
err := p.DB.Get(&r, `
|
||||||
|
@ -12,9 +12,7 @@ type SpaceParticipant struct {
|
|||||||
SpaceID scraper.SpaceID `db:"space_id"`
|
SpaceID scraper.SpaceID `db:"space_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Save a Space
|
||||||
* Save a Space
|
|
||||||
*/
|
|
||||||
func (p Profile) SaveSpace(s scraper.Space) error {
|
func (p Profile) SaveSpace(s scraper.Space) error {
|
||||||
_, err := p.DB.NamedExec(`
|
_, err := p.DB.NamedExec(`
|
||||||
insert into spaces (id, created_by_id, short_url, state, title, created_at, started_at, ended_at, updated_at,
|
insert into spaces (id, created_by_id, short_url, state, title, created_at, started_at, ended_at, updated_at,
|
||||||
@ -53,9 +51,7 @@ func (p Profile) SaveSpace(s scraper.Space) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Get a Space by ID
|
||||||
* Get a Space by ID
|
|
||||||
*/
|
|
||||||
func (p Profile) GetSpaceById(id scraper.SpaceID) (space scraper.Space, err error) {
|
func (p Profile) GetSpaceById(id scraper.SpaceID) (space scraper.Space, err error) {
|
||||||
err = p.DB.Get(&space,
|
err = p.DB.Get(&space,
|
||||||
`select id, created_by_id, short_url, state, title, created_at, started_at, ended_at, updated_at, is_available_for_replay,
|
`select id, created_by_id, short_url, state, title, created_at, started_at, ended_at, updated_at, is_available_for_replay,
|
||||||
|
@ -5,11 +5,10 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
. "gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (p Profile) SaveTweet(t Tweet) error {
|
||||||
func (p Profile) SaveTweet(t scraper.Tweet) error {
|
|
||||||
db := p.DB
|
db := p.DB
|
||||||
|
|
||||||
tx := db.MustBegin()
|
tx := db.MustBegin()
|
||||||
@ -104,7 +103,7 @@ func (p Profile) SaveTweet(t scraper.Tweet) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Profile) IsTweetInDatabase(id scraper.TweetID) bool {
|
func (p Profile) IsTweetInDatabase(id TweetID) bool {
|
||||||
db := p.DB
|
db := p.DB
|
||||||
|
|
||||||
var dummy string
|
var dummy string
|
||||||
@ -119,10 +118,10 @@ func (p Profile) IsTweetInDatabase(id scraper.TweetID) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Profile) GetTweetById(id scraper.TweetID) (scraper.Tweet, error) {
|
func (p Profile) GetTweetById(id TweetID) (Tweet, error) {
|
||||||
db := p.DB
|
db := p.DB
|
||||||
|
|
||||||
var t scraper.Tweet
|
var t Tweet
|
||||||
err := db.Get(&t, `
|
err := db.Get(&t, `
|
||||||
select id, user_id, text, posted_at, num_likes, num_retweets, num_replies, num_quote_tweets, in_reply_to_id, quoted_tweet_id,
|
select id, user_id, text, posted_at, num_likes, num_retweets, num_replies, num_quote_tweets, in_reply_to_id, quoted_tweet_id,
|
||||||
mentions, reply_mentions, hashtags, ifnull(space_id, '') space_id, ifnull(tombstone_types.short_name, "") tombstone_type,
|
mentions, reply_mentions, hashtags, ifnull(space_id, '') space_id, ifnull(tombstone_types.short_name, "") tombstone_type,
|
||||||
@ -133,10 +132,10 @@ func (p Profile) GetTweetById(id scraper.TweetID) (scraper.Tweet, error) {
|
|||||||
`, id)
|
`, id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return scraper.Tweet{}, fmt.Errorf("Error executing GetTweetByID(%d):\n %w", id, err)
|
return Tweet{}, fmt.Errorf("Error executing GetTweetByID(%d):\n %w", id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Spaces = []scraper.Space{}
|
t.Spaces = []Space{}
|
||||||
if t.SpaceID != "" {
|
if t.SpaceID != "" {
|
||||||
space, err := p.GetSpaceById(t.SpaceID)
|
space, err := p.GetSpaceById(t.SpaceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -172,10 +171,8 @@ func (p Profile) GetTweetById(id scraper.TweetID) (scraper.Tweet, error) {
|
|||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Populate the `User` field on a tweet with an actual User
|
||||||
* Populate the `User` field on a tweet with an actual User
|
func (p Profile) LoadUserFor(t *Tweet) error {
|
||||||
*/
|
|
||||||
func (p Profile) LoadUserFor(t *scraper.Tweet) error {
|
|
||||||
if t.User != nil {
|
if t.User != nil {
|
||||||
// Already there, no need to load it
|
// Already there, no need to load it
|
||||||
return nil
|
return nil
|
||||||
@ -189,10 +186,8 @@ func (p Profile) LoadUserFor(t *scraper.Tweet) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Return `false` if the tweet is in the DB and has had its content downloaded, `false` otherwise
|
||||||
* Return `false` if the tweet is in the DB and has had its content downloaded, `false` otherwise
|
func (p Profile) CheckTweetContentDownloadNeeded(tweet Tweet) bool {
|
||||||
*/
|
|
||||||
func (p Profile) CheckTweetContentDownloadNeeded(tweet scraper.Tweet) bool {
|
|
||||||
row := p.DB.QueryRow(`select is_content_downloaded from tweets where id = ?`, tweet.ID)
|
row := p.DB.QueryRow(`select is_content_downloaded from tweets where id = ?`, tweet.ID)
|
||||||
|
|
||||||
var is_content_downloaded bool
|
var is_content_downloaded bool
|
||||||
|
@ -11,9 +11,7 @@ import (
|
|||||||
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
// Create a Tweet, save it, reload it, and make sure it comes back the same
|
||||||
* Create a Tweet, save it, reload it, and make sure it comes back the same
|
|
||||||
*/
|
|
||||||
func TestSaveAndLoadTweet(t *testing.T) {
|
func TestSaveAndLoadTweet(t *testing.T) {
|
||||||
profile_path := "test_profiles/TestTweetQueries"
|
profile_path := "test_profiles/TestTweetQueries"
|
||||||
profile := create_or_load_profile(profile_path)
|
profile := create_or_load_profile(profile_path)
|
||||||
@ -34,9 +32,7 @@ func TestSaveAndLoadTweet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Same as above, but with a tombstone
|
||||||
* Same as above, but with a tombstone
|
|
||||||
*/
|
|
||||||
func TestSaveAndLoadTombstone(t *testing.T) {
|
func TestSaveAndLoadTombstone(t *testing.T) {
|
||||||
profile_path := "test_profiles/TestTweetQueries"
|
profile_path := "test_profiles/TestTweetQueries"
|
||||||
profile := create_or_load_profile(profile_path)
|
profile := create_or_load_profile(profile_path)
|
||||||
@ -56,14 +52,12 @@ func TestSaveAndLoadTombstone(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Saving a tweet that already exists shouldn't reduce its backed-up status.
|
||||||
* Saving a tweet that already exists shouldn't reduce its backed-up status.
|
// i.e., content which is already saved shouldn't be marked un-saved if it's removed from Twitter.
|
||||||
* i.e., content which is already saved shouldn't be marked un-saved if it's removed from Twitter.
|
// After all, that's the whole point of archiving.
|
||||||
* After all, that's the whole point of archiving.
|
//
|
||||||
*
|
// - is_stub should only go from "yes" to "no"
|
||||||
* - is_stub should only go from "yes" to "no"
|
// - is_content_downloaded should only go from "no" to "yes"
|
||||||
* - is_content_downloaded should only go from "no" to "yes"
|
|
||||||
*/
|
|
||||||
func TestNoWorseningTweet(t *testing.T) {
|
func TestNoWorseningTweet(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
@ -117,9 +111,7 @@ func TestNoWorseningTweet(t *testing.T) {
|
|||||||
assert.Equal(13, new_tweet.NumReplies)
|
assert.Equal(13, new_tweet.NumReplies)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// The tweet was a tombstone and is now available; it should be updated
|
||||||
* The tweet was a tombstone and is now available; it should be updated
|
|
||||||
*/
|
|
||||||
func TestUntombstoningTweet(t *testing.T) {
|
func TestUntombstoningTweet(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
@ -182,10 +174,8 @@ func TestUpgradingExpandingTweet(t *testing.T) {
|
|||||||
assert.Equal(new_tweet.Text, "Some long but cut-off text, but now it no longer is cut off!", "Should have extended the text")
|
assert.Equal(new_tweet.Text, "Some long but cut-off text, but now it no longer is cut off!", "Should have extended the text")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// The "unavailable" tombstone type is not reliable, you should be able to update away from it but
|
||||||
* The "unavailable" tombstone type is not reliable, you should be able to update away from it but
|
// not toward it
|
||||||
* not toward it
|
|
||||||
*/
|
|
||||||
func TestChangingTombstoningTweet(t *testing.T) {
|
func TestChangingTombstoningTweet(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
@ -273,9 +263,7 @@ func TestModifyTweet(t *testing.T) {
|
|||||||
assert.Equal(new_tweet.TombstoneType, "deleted")
|
assert.Equal(new_tweet.TombstoneType, "deleted")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Should correctly report whether the User exists in the database
|
||||||
* Should correctly report whether the User exists in the database
|
|
||||||
*/
|
|
||||||
func TestIsTweetInDatabase(t *testing.T) {
|
func TestIsTweetInDatabase(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
profile_path := "test_profiles/TestTweetQueries"
|
profile_path := "test_profiles/TestTweetQueries"
|
||||||
@ -293,9 +281,7 @@ func TestIsTweetInDatabase(t *testing.T) {
|
|||||||
assert.True(t, exists)
|
assert.True(t, exists)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Should correctly populate the `User` field on a Tweet
|
||||||
* Should correctly populate the `User` field on a Tweet
|
|
||||||
*/
|
|
||||||
func TestLoadUserForTweet(t *testing.T) {
|
func TestLoadUserForTweet(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
profile_path := "test_profiles/TestTweetQueries"
|
profile_path := "test_profiles/TestTweetQueries"
|
||||||
@ -313,9 +299,7 @@ func TestLoadUserForTweet(t *testing.T) {
|
|||||||
require.NotNil(tweet.User, "Did not load a user. It is still nil.")
|
require.NotNil(tweet.User, "Did not load a user. It is still nil.")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Test all the combinations for whether a tweet needs its content downloaded
|
||||||
* Test all the combinations for whether a tweet needs its content downloaded
|
|
||||||
*/
|
|
||||||
func TestCheckTweetContentDownloadNeeded(t *testing.T) {
|
func TestCheckTweetContentDownloadNeeded(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
profile_path := "test_profiles/TestTweetQueries"
|
profile_path := "test_profiles/TestTweetQueries"
|
||||||
|
@ -6,10 +6,8 @@ import (
|
|||||||
. "gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
. "gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
// Convenience function that saves all the objects in a TweetTrove.
|
||||||
* Convenience function that saves all the objects in a TweetTrove.
|
// Panics if anything goes wrong.
|
||||||
* Panics if anything goes wrong.
|
|
||||||
*/
|
|
||||||
func (p Profile) SaveTweetTrove(trove TweetTrove) {
|
func (p Profile) SaveTweetTrove(trove TweetTrove) {
|
||||||
for i, u := range trove.Users {
|
for i, u := range trove.Users {
|
||||||
err := p.SaveUser(&u)
|
err := p.SaveUser(&u)
|
||||||
|
@ -9,13 +9,11 @@ import (
|
|||||||
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
// Save the given User to the database.
|
||||||
* Save the given User to the database.
|
// If the User is already in the database, it will update most of its attributes (follower count, etc)
|
||||||
* If the User is already in the database, it will update most of its attributes (follower count, etc)
|
//
|
||||||
*
|
// args:
|
||||||
* args:
|
// - u: the User
|
||||||
* - u: the User
|
|
||||||
*/
|
|
||||||
func (p Profile) SaveUser(u *scraper.User) error {
|
func (p Profile) SaveUser(u *scraper.User) error {
|
||||||
if u.IsNeedingFakeID {
|
if u.IsNeedingFakeID {
|
||||||
err := p.DB.QueryRow("select id from users where lower(handle) = lower(?)", u.Handle).Scan(&u.ID)
|
err := p.DB.QueryRow("select id from users where lower(handle) = lower(?)", u.Handle).Scan(&u.ID)
|
||||||
@ -67,15 +65,13 @@ func (p Profile) SaveUser(u *scraper.User) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Check if the database has a User with the given user handle.
|
||||||
* Check if the database has a User with the given user handle.
|
//
|
||||||
*
|
// args:
|
||||||
* args:
|
// - handle: the user handle to search for
|
||||||
* - handle: the user handle to search for
|
//
|
||||||
*
|
// returns:
|
||||||
* returns:
|
// - true if there is such a User in the database, false otherwise
|
||||||
* - true if there is such a User in the database, false otherwise
|
|
||||||
*/
|
|
||||||
func (p Profile) UserExists(handle scraper.UserHandle) bool {
|
func (p Profile) UserExists(handle scraper.UserHandle) bool {
|
||||||
db := p.DB
|
db := p.DB
|
||||||
|
|
||||||
@ -91,15 +87,13 @@ func (p Profile) UserExists(handle scraper.UserHandle) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Retrieve a User from the database, by handle.
|
||||||
* Retrieve a User from the database, by handle.
|
//
|
||||||
*
|
// args:
|
||||||
* args:
|
// - handle: the user handle to search for
|
||||||
* - handle: the user handle to search for
|
//
|
||||||
*
|
// returns:
|
||||||
* returns:
|
// - the User, if it exists
|
||||||
* - the User, if it exists
|
|
||||||
*/
|
|
||||||
func (p Profile) GetUserByHandle(handle scraper.UserHandle) (scraper.User, error) {
|
func (p Profile) GetUserByHandle(handle scraper.UserHandle) (scraper.User, error) {
|
||||||
db := p.DB
|
db := p.DB
|
||||||
|
|
||||||
@ -118,15 +112,13 @@ func (p Profile) GetUserByHandle(handle scraper.UserHandle) (scraper.User, error
|
|||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Retrieve a User from the database, by user ID.
|
||||||
* Retrieve a User from the database, by user ID.
|
//
|
||||||
*
|
// args:
|
||||||
* args:
|
// - id: the user ID to search for
|
||||||
* - id: the user ID to search for
|
//
|
||||||
*
|
// returns:
|
||||||
* returns:
|
// - the User, if it exists
|
||||||
* - the User, if it exists
|
|
||||||
*/
|
|
||||||
func (p Profile) GetUserByID(id scraper.UserID) (scraper.User, error) {
|
func (p Profile) GetUserByID(id scraper.UserID) (scraper.User, error) {
|
||||||
db := p.DB
|
db := p.DB
|
||||||
|
|
||||||
@ -148,20 +140,18 @@ func (p Profile) GetUserByID(id scraper.UserID) (scraper.User, error) {
|
|||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Returns `true` if content download is needed, `false` otherwise
|
||||||
* Returns `true` if content download is needed, `false` otherwise
|
//
|
||||||
*
|
// If the user is banned, returns false because downloading will be impossible.
|
||||||
* If the user is banned, returns false because downloading will be impossible.
|
//
|
||||||
*
|
// If:
|
||||||
* If:
|
// - the user isn't in the DB at all (first time scraping), OR
|
||||||
* - the user isn't in the DB at all (first time scraping), OR
|
// - `is_content_downloaded` is false in the DB, OR
|
||||||
* - `is_content_downloaded` is false in the DB, OR
|
// - the banner / profile image URL has changed from what the DB has
|
||||||
* - the banner / profile image URL has changed from what the DB has
|
// then it needs to be downloaded.
|
||||||
* then it needs to be downloaded.
|
//
|
||||||
*
|
// The `user` object will always have `is_content_downloaded` = false on every scrape. This is
|
||||||
* The `user` object will always have `is_content_downloaded` = false on every scrape. This is
|
// why the No Worsening Principle is needed.
|
||||||
* why the No Worsening Principle is needed.
|
|
||||||
*/
|
|
||||||
func (p Profile) CheckUserContentDownloadNeeded(user scraper.User) bool {
|
func (p Profile) CheckUserContentDownloadNeeded(user scraper.User) bool {
|
||||||
row := p.DB.QueryRow(`select is_content_downloaded, profile_image_url, banner_image_url from users where id = ?`, user.ID)
|
row := p.DB.QueryRow(`select is_content_downloaded, profile_image_url, banner_image_url from users where id = ?`, user.ID)
|
||||||
|
|
||||||
@ -189,9 +179,7 @@ func (p Profile) CheckUserContentDownloadNeeded(user scraper.User) bool {
|
|||||||
return !file_exists(profile_path)
|
return !file_exists(profile_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Follow / unfollow a user. Update the given User object's IsFollowed field.
|
||||||
* Follow / unfollow a user. Update the given User object's IsFollowed field.
|
|
||||||
*/
|
|
||||||
func (p Profile) SetUserFollowed(user *scraper.User, is_followed bool) {
|
func (p Profile) SetUserFollowed(user *scraper.User, is_followed bool) {
|
||||||
result, err := p.DB.Exec("update users set is_followed = ? where id = ?", is_followed, user.ID)
|
result, err := p.DB.Exec("update users set is_followed = ? where id = ?", is_followed, user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -254,16 +242,12 @@ func (p Profile) IsFollowing(user scraper.User) bool {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Utility function to compute the path to save banner image to
|
||||||
* Utility function to compute the path to save banner image to
|
|
||||||
*/
|
|
||||||
func (p Profile) get_banner_image_output_path(u scraper.User) string {
|
func (p Profile) get_banner_image_output_path(u scraper.User) string {
|
||||||
return path.Join(p.ProfileDir, "profile_images", u.BannerImageLocalPath)
|
return path.Join(p.ProfileDir, "profile_images", u.BannerImageLocalPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Utility function to compute the path to save profile image to
|
||||||
* Utility function to compute the path to save profile image to
|
|
||||||
*/
|
|
||||||
func (p Profile) get_profile_image_output_path(u scraper.User) string {
|
func (p Profile) get_profile_image_output_path(u scraper.User) string {
|
||||||
if u.ProfileImageUrl == "" {
|
if u.ProfileImageUrl == "" {
|
||||||
return path.Join(p.ProfileDir, "profile_images", path.Base(scraper.DEFAULT_PROFILE_IMAGE_URL))
|
return path.Join(p.ProfileDir, "profile_images", path.Base(scraper.DEFAULT_PROFILE_IMAGE_URL))
|
||||||
|
@ -14,9 +14,7 @@ import (
|
|||||||
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
// Create a user, save it, reload it, and make sure it comes back the same
|
||||||
* Create a user, save it, reload it, and make sure it comes back the same
|
|
||||||
*/
|
|
||||||
func TestSaveAndLoadUser(t *testing.T) {
|
func TestSaveAndLoadUser(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
profile_path := "test_profiles/TestUserQueries"
|
profile_path := "test_profiles/TestUserQueries"
|
||||||
@ -44,9 +42,6 @@ func TestSaveAndLoadUser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
func TestModifyUser(t *testing.T) {
|
func TestModifyUser(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
@ -117,9 +112,7 @@ func TestHandleIsCaseInsensitive(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Should correctly report whether the user exists in the database
|
||||||
* Should correctly report whether the user exists in the database
|
|
||||||
*/
|
|
||||||
func TestUserExists(t *testing.T) {
|
func TestUserExists(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
profile_path := "test_profiles/TestUserQueries"
|
profile_path := "test_profiles/TestUserQueries"
|
||||||
@ -137,9 +130,7 @@ func TestUserExists(t *testing.T) {
|
|||||||
require.True(exists)
|
require.True(exists)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Test scenarios relating to user content downloading
|
||||||
* Test scenarios relating to user content downloading
|
|
||||||
*/
|
|
||||||
func TestCheckUserContentDownloadNeeded(t *testing.T) {
|
func TestCheckUserContentDownloadNeeded(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
profile_path := "test_profiles/TestUserQueries"
|
profile_path := "test_profiles/TestUserQueries"
|
||||||
@ -177,14 +168,12 @@ func TestCheckUserContentDownloadNeeded(t *testing.T) {
|
|||||||
assert.True(profile.CheckUserContentDownloadNeeded(user))
|
assert.True(profile.CheckUserContentDownloadNeeded(user))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Make sure following works
|
||||||
* Make sure following works
|
//
|
||||||
*
|
// - users are unfollowed by default
|
||||||
* - users are unfollowed by default
|
// - following a user makes it save as is_followed
|
||||||
* - following a user makes it save as is_followed
|
// - using regular save method doesn't un-follow
|
||||||
* - using regular save method doesn't un-follow
|
// - unfollowing a user makes it save as no longer is_followed
|
||||||
* - unfollowing a user makes it save as no longer is_followed
|
|
||||||
*/
|
|
||||||
func TestFollowUnfollowUser(t *testing.T) {
|
func TestFollowUnfollowUser(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
@ -222,9 +211,7 @@ func TestFollowUnfollowUser(t *testing.T) {
|
|||||||
assert.False(user_reloaded.IsFollowed)
|
assert.False(user_reloaded.IsFollowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Should correctly report whether a User is followed or not, according to the DB (not the in-memory objects)
|
||||||
* Should correctly report whether a User is followed or not, according to the DB (not the in-memory objects)
|
|
||||||
*/
|
|
||||||
func TestIsFollowingUser(t *testing.T) {
|
func TestIsFollowingUser(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
@ -256,10 +243,8 @@ func TestIsFollowingUser(t *testing.T) {
|
|||||||
assert.False(profile.IsFollowing(user))
|
assert.False(profile.IsFollowing(user))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Should create a new Unknown User from the given handle.
|
||||||
* Should create a new Unknown User from the given handle.
|
// The Unknown User should work consistently with other Users.
|
||||||
* The Unknown User should work consistently with other Users.
|
|
||||||
*/
|
|
||||||
func TestCreateUnknownUserWithHandle(t *testing.T) {
|
func TestCreateUnknownUserWithHandle(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
@ -287,9 +272,7 @@ func TestCreateUnknownUserWithHandle(t *testing.T) {
|
|||||||
assert.Equal(next_id+2, profile.NextFakeUserID())
|
assert.Equal(next_id+2, profile.NextFakeUserID())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Should update the unknown User's UserID with the correct ID if it already exists
|
||||||
* Should update the unknown User's UserID with the correct ID if it already exists
|
|
||||||
*/
|
|
||||||
func TestCreateUnknownUserWithHandleThatAlreadyExists(t *testing.T) {
|
func TestCreateUnknownUserWithHandleThatAlreadyExists(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
@ -31,9 +31,7 @@ func file_exists(path string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// https://stackoverflow.com/questions/56616196/how-to-convert-camel-case-string-to-snake-case#56616250
|
||||||
* https://stackoverflow.com/questions/56616196/how-to-convert-camel-case-string-to-snake-case#56616250
|
|
||||||
*/
|
|
||||||
func ToSnakeCase(str string) string {
|
func ToSnakeCase(str string) string {
|
||||||
snake := regexp.MustCompile("(.)_?([A-Z][a-z]+)").ReplaceAllString(str, "${1}_${2}")
|
snake := regexp.MustCompile("(.)_?([A-Z][a-z]+)").ReplaceAllString(str, "${1}_${2}")
|
||||||
snake = regexp.MustCompile("([a-z0-9])_?([A-Z])").ReplaceAllString(snake, "${1}_${2}")
|
snake = regexp.MustCompile("([a-z0-9])_?([A-Z])").ReplaceAllString(snake, "${1}_${2}")
|
||||||
|
@ -9,9 +9,7 @@ import (
|
|||||||
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
// Load a test profile, or create it if it doesn't exist.
|
||||||
* Load a test profile, or create it if it doesn't exist.
|
|
||||||
*/
|
|
||||||
func create_or_load_profile(profile_path string) persistence.Profile {
|
func create_or_load_profile(profile_path string) persistence.Profile {
|
||||||
var profile persistence.Profile
|
var profile persistence.Profile
|
||||||
var err error
|
var err error
|
||||||
@ -40,9 +38,7 @@ func create_or_load_profile(profile_path string) persistence.Profile {
|
|||||||
return profile
|
return profile
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Create a stable user with a fixed ID and handle
|
||||||
* Create a stable user with a fixed ID and handle
|
|
||||||
*/
|
|
||||||
func create_stable_user() scraper.User {
|
func create_stable_user() scraper.User {
|
||||||
return scraper.User{
|
return scraper.User{
|
||||||
ID: scraper.UserID(-1),
|
ID: scraper.UserID(-1),
|
||||||
@ -64,9 +60,7 @@ func create_stable_user() scraper.User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Create a semi-stable Image based on the given ID
|
||||||
* Create a semi-stable Image based on the given ID
|
|
||||||
*/
|
|
||||||
func create_image_from_id(id int) scraper.Image {
|
func create_image_from_id(id int) scraper.Image {
|
||||||
filename := fmt.Sprintf("image%d.jpg", id)
|
filename := fmt.Sprintf("image%d.jpg", id)
|
||||||
return scraper.Image{
|
return scraper.Image{
|
||||||
@ -80,9 +74,7 @@ func create_image_from_id(id int) scraper.Image {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Create a semi-stable Video based on the given ID
|
||||||
* Create a semi-stable Video based on the given ID
|
|
||||||
*/
|
|
||||||
func create_video_from_id(id int) scraper.Video {
|
func create_video_from_id(id int) scraper.Video {
|
||||||
filename := fmt.Sprintf("video%d.jpg", id)
|
filename := fmt.Sprintf("video%d.jpg", id)
|
||||||
return scraper.Video{
|
return scraper.Video{
|
||||||
@ -101,9 +93,7 @@ func create_video_from_id(id int) scraper.Video {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Create a semi-stable Url based on the given ID
|
||||||
* Create a semi-stable Url based on the given ID
|
|
||||||
*/
|
|
||||||
func create_url_from_id(id int) scraper.Url {
|
func create_url_from_id(id int) scraper.Url {
|
||||||
s := fmt.Sprint(id)
|
s := fmt.Sprint(id)
|
||||||
return scraper.Url{
|
return scraper.Url{
|
||||||
@ -124,9 +114,7 @@ func create_url_from_id(id int) scraper.Url {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Create a semi-stable Poll based on the given ID
|
||||||
* Create a semi-stable Poll based on the given ID
|
|
||||||
*/
|
|
||||||
func create_poll_from_id(id int) scraper.Poll {
|
func create_poll_from_id(id int) scraper.Poll {
|
||||||
s := fmt.Sprint(id)
|
s := fmt.Sprint(id)
|
||||||
return scraper.Poll{
|
return scraper.Poll{
|
||||||
@ -143,9 +131,7 @@ func create_poll_from_id(id int) scraper.Poll {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Create a stable tweet with a fixed ID and content
|
||||||
* Create a stable tweet with a fixed ID and content
|
|
||||||
*/
|
|
||||||
func create_stable_tweet() scraper.Tweet {
|
func create_stable_tweet() scraper.Tweet {
|
||||||
tweet_id := scraper.TweetID(-1)
|
tweet_id := scraper.TweetID(-1)
|
||||||
return scraper.Tweet{
|
return scraper.Tweet{
|
||||||
@ -180,9 +166,7 @@ func create_stable_tweet() scraper.Tweet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Create a stable retweet with a fixed ID and parameters
|
||||||
* Create a stable retweet with a fixed ID and parameters
|
|
||||||
*/
|
|
||||||
func create_stable_retweet() scraper.Retweet {
|
func create_stable_retweet() scraper.Retweet {
|
||||||
retweet_id := scraper.TweetID(-1)
|
retweet_id := scraper.TweetID(-1)
|
||||||
return scraper.Retweet{
|
return scraper.Retweet{
|
||||||
@ -193,9 +177,7 @@ func create_stable_retweet() scraper.Retweet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Create a new user with a random ID and handle
|
||||||
* Create a new user with a random ID and handle
|
|
||||||
*/
|
|
||||||
func create_dummy_user() scraper.User {
|
func create_dummy_user() scraper.User {
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
userID := rand.Int()
|
userID := rand.Int()
|
||||||
@ -220,9 +202,7 @@ func create_dummy_user() scraper.User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Create a new tweet with a random ID and content
|
||||||
* Create a new tweet with a random ID and content
|
|
||||||
*/
|
|
||||||
func create_dummy_tweet() scraper.Tweet {
|
func create_dummy_tweet() scraper.Tweet {
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
tweet_id := scraper.TweetID(rand.Int())
|
tweet_id := scraper.TweetID(rand.Int())
|
||||||
@ -266,9 +246,7 @@ func create_dummy_tweet() scraper.Tweet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Create a random tombstone
|
||||||
* Create a random tombstone
|
|
||||||
*/
|
|
||||||
func create_dummy_tombstone() scraper.Tweet {
|
func create_dummy_tombstone() scraper.Tweet {
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
tweet_id := scraper.TweetID(rand.Int())
|
tweet_id := scraper.TweetID(rand.Int())
|
||||||
@ -285,9 +263,7 @@ func create_dummy_tombstone() scraper.Tweet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Create a new retweet with a random ID for a given TweetID
|
||||||
* Create a new retweet with a random ID for a given TweetID
|
|
||||||
*/
|
|
||||||
func create_dummy_retweet(tweet_id scraper.TweetID) scraper.Retweet {
|
func create_dummy_retweet(tweet_id scraper.TweetID) scraper.Retweet {
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
retweet_id := scraper.TweetID(rand.Int())
|
retweet_id := scraper.TweetID(rand.Int())
|
||||||
@ -300,9 +276,7 @@ func create_dummy_retweet(tweet_id scraper.TweetID) scraper.Retweet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Create a semi-stable Space given an ID
|
||||||
* Create a semi-stable Space given an ID
|
|
||||||
*/
|
|
||||||
func create_space_from_id(id int) scraper.Space {
|
func create_space_from_id(id int) scraper.Space {
|
||||||
return scraper.Space{
|
return scraper.Space{
|
||||||
ID: scraper.SpaceID(fmt.Sprintf("some_id_%d", id)),
|
ID: scraper.SpaceID(fmt.Sprintf("some_id_%d", id)),
|
||||||
|
@ -24,51 +24,51 @@ Please upgrade this application to a newer version to use this profile. Or down
|
|||||||
// The Nth entry is the migration that moves you from version N to version N+1.
|
// The Nth entry is the migration that moves you from version N to version N+1.
|
||||||
var MIGRATIONS = []string{
|
var MIGRATIONS = []string{
|
||||||
`create table polls (rowid integer primary key,
|
`create table polls (rowid integer primary key,
|
||||||
id integer unique not null check(typeof(id) = 'integer'),
|
id integer unique not null check(typeof(id) = 'integer'),
|
||||||
tweet_id integer not null,
|
tweet_id integer not null,
|
||||||
num_choices integer not null,
|
num_choices integer not null,
|
||||||
|
|
||||||
choice1 text,
|
choice1 text,
|
||||||
choice1_votes integer,
|
choice1_votes integer,
|
||||||
choice2 text,
|
choice2 text,
|
||||||
choice2_votes integer,
|
choice2_votes integer,
|
||||||
choice3 text,
|
choice3 text,
|
||||||
choice3_votes integer,
|
choice3_votes integer,
|
||||||
choice4 text,
|
choice4 text,
|
||||||
choice4_votes integer,
|
choice4_votes integer,
|
||||||
|
|
||||||
voting_duration integer not null, -- in seconds
|
voting_duration integer not null, -- in seconds
|
||||||
voting_ends_at integer not null,
|
voting_ends_at integer not null,
|
||||||
|
|
||||||
last_scraped_at integer not null,
|
last_scraped_at integer not null,
|
||||||
|
|
||||||
foreign key(tweet_id) references tweets(id)
|
foreign key(tweet_id) references tweets(id)
|
||||||
);`,
|
);`,
|
||||||
`alter table tweets add column is_conversation_scraped boolean default 0;
|
`alter table tweets add column is_conversation_scraped boolean default 0;
|
||||||
alter table tweets add column last_scraped_at integer not null default 0`,
|
alter table tweets add column last_scraped_at integer not null default 0`,
|
||||||
`update tombstone_types set tombstone_text = 'This Tweet is from a suspended account' where rowid = 2;
|
`update tombstone_types set tombstone_text = 'This Tweet is from a suspended account' where rowid = 2;
|
||||||
insert into tombstone_types (rowid, short_name, tombstone_text)
|
insert into tombstone_types (rowid, short_name, tombstone_text)
|
||||||
values (5, 'violated', 'This Tweet violated the Twitter Rules'),
|
values (5, 'violated', 'This Tweet violated the Twitter Rules'),
|
||||||
(6, 'no longer exists', 'This Tweet is from an account that no longer exists')`,
|
(6, 'no longer exists', 'This Tweet is from an account that no longer exists')`,
|
||||||
`alter table videos add column thumbnail_remote_url text not null default "missing";
|
`alter table videos add column thumbnail_remote_url text not null default "missing";
|
||||||
alter table videos add column thumbnail_local_filename text not null default "missing"`,
|
alter table videos add column thumbnail_local_filename text not null default "missing"`,
|
||||||
`alter table videos add column duration integer not null default 0;
|
`alter table videos add column duration integer not null default 0;
|
||||||
alter table videos add column view_count integer not null default 0`,
|
alter table videos add column view_count integer not null default 0`,
|
||||||
`alter table users add column is_banned boolean default 0`,
|
`alter table users add column is_banned boolean default 0`,
|
||||||
`alter table urls add column short_text text not null default ""`,
|
`alter table urls add column short_text text not null default ""`,
|
||||||
`insert into tombstone_types (rowid, short_name, tombstone_text) values (7, 'age-restricted', 'Age-restricted adult content. '
|
`insert into tombstone_types (rowid, short_name, tombstone_text) values (7, 'age-restricted', 'Age-restricted adult content. '
|
||||||
|| 'This content might not be appropriate for people under 18 years old. To view this media, you’ll need to log in to Twitter')`,
|
|| 'This content might not be appropriate for people under 18 years old. To view this media, you’ll need to log in to Twitter')`,
|
||||||
`alter table users add column is_followed boolean default 0`,
|
`alter table users add column is_followed boolean default 0`,
|
||||||
`create table fake_user_sequence(latest_fake_id integer not null);
|
`create table fake_user_sequence(latest_fake_id integer not null);
|
||||||
insert into fake_user_sequence values(0x4000000000000000);
|
insert into fake_user_sequence values(0x4000000000000000);
|
||||||
alter table users add column is_id_fake boolean default 0;`,
|
alter table users add column is_id_fake boolean default 0;`,
|
||||||
`delete from urls where rowid in (select urls.rowid from tweets join urls on tweets.id = urls.tweet_id where urls.text like
|
`delete from urls where rowid in (select urls.rowid from tweets join urls on tweets.id = urls.tweet_id where urls.text like
|
||||||
'https://twitter.com/%/status/' || tweets.quoted_tweet_id || "%")`,
|
'https://twitter.com/%/status/' || tweets.quoted_tweet_id || "%")`,
|
||||||
`create table spaces(rowid integer primary key,
|
`create table spaces(rowid integer primary key,
|
||||||
id text unique not null,
|
id text unique not null,
|
||||||
short_url text not null
|
short_url text not null
|
||||||
);
|
);
|
||||||
alter table tweets add column space_id text references spaces(id)`,
|
alter table tweets add column space_id text references spaces(id)`,
|
||||||
`alter table videos add column is_blocked_by_dmca boolean not null default 0`,
|
`alter table videos add column is_blocked_by_dmca boolean not null default 0`,
|
||||||
`create index if not exists index_tweets_in_reply_to_id on tweets (in_reply_to_id);
|
`create index if not exists index_tweets_in_reply_to_id on tweets (in_reply_to_id);
|
||||||
create index if not exists index_urls_tweet_id on urls (tweet_id);
|
create index if not exists index_urls_tweet_id on urls (tweet_id);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user