2020-11-28 14:40:46 +00:00
// SPDX-License-Identifier: MIT
2020-11-22 15:17:14 +00:00
# include "yt.h"
# include <sqlite3.h>
# include <nlohmann/json.hpp>
# include <curl/curl.h>
2021-07-18 14:11:16 +00:00
# include <cinttypes>
2020-11-22 15:17:14 +00:00
# include "tui.h"
2020-11-22 18:18:45 +00:00
# include "db.h"
2020-11-22 15:17:14 +00:00
using json = nlohmann : : json ;
struct yt_config yt_config ;
2021-07-18 14:11:16 +00:00
UserFlag : : UserFlag ( sqlite3_stmt * row ) : id ( get_int ( row , 0 ) ) , name ( get_string ( row , 1 ) ) { }
2021-07-18 21:27:20 +00:00
void UserFlag : : save ( sqlite3 * db ) const
{
sqlite3_stmt * query ;
SC ( sqlite3_prepare_v2 ( db , " UPDATE user_flags SET name = ?2 WHERE flagId = ?1; " , - 1 , & query , nullptr ) ) ;
SC ( sqlite3_bind_int ( query , 1 , id ) ) ;
SC ( sqlite3_bind_text ( query , 2 , name . c_str ( ) , - 1 , SQLITE_TRANSIENT ) ) ;
SC ( sqlite3_step ( query ) ) ;
SC ( sqlite3_finalize ( query ) ) ;
}
2021-07-18 14:11:16 +00:00
UserFlag UserFlag : : create ( sqlite3 * db , const std : : string & name )
{
int next_flag = next_free ( db ) ;
if ( next_flag = = - 1 ) {
tui_abort ( " Out of UserFlags... " ) ;
}
sqlite3_stmt * query ;
SC ( sqlite3_prepare_v2 ( db , " INSERT INTO user_flags(flagId, name) values(?1, ?2); " , - 1 , & query , nullptr ) ) ;
SC ( sqlite3_bind_int ( query , 1 , next_flag ) ) ;
SC ( sqlite3_bind_text ( query , 2 , name . c_str ( ) , - 1 , nullptr ) ) ;
SC ( sqlite3_step ( query ) ) ;
SC ( sqlite3_finalize ( query ) ) ;
return UserFlag ( next_flag , name ) ;
}
int UserFlag : : next_free ( sqlite3 * db )
{
int64_t flag = 1 ;
sqlite3_stmt * query ;
SC ( sqlite3_prepare_v2 ( db , " SELECT flagId FROM user_flags ORDER BY flagId; " , - 1 , & query , nullptr ) ) ;
while ( sqlite3_step ( query ) = = SQLITE_ROW ) {
const int fid = get_int ( query , 0 ) ;
if ( flag ! = fid ) {
tui_abort ( " Invalid UserFlag " PRId64 " . Expected " PRId64 , fid , flag ) ;
}
flag < < = 1 ;
}
SC ( sqlite3_finalize ( query ) ) ;
if ( flag > ( 2L < < 32 ) )
return - 1 ;
return flag ;
}
std : : vector < UserFlag > UserFlag : : get_all ( sqlite3 * db )
{
std : : vector < UserFlag > out ;
sqlite3_stmt * query ;
SC ( sqlite3_prepare_v2 ( db , " SELECT * FROM user_flags ORDER BY flagId; " , - 1 , & query , nullptr ) ) ;
while ( sqlite3_step ( query ) = = SQLITE_ROW ) {
out . emplace_back ( query ) ;
}
SC ( sqlite3_finalize ( query ) ) ;
return out ;
}
UserFlag : : UserFlag ( int id , const std : : string & name ) : id ( id ) , name ( name ) { }
2020-11-22 15:17:14 +00:00
static size_t curl_writecallback ( void * data , size_t size , size_t nmemb , void * userp )
{
size_t to_add = size * nmemb ;
std : : vector < unsigned char > * buffer = reinterpret_cast < std : : vector < unsigned char > * > ( userp ) ;
const size_t cur_size = buffer - > size ( ) ;
buffer - > resize ( cur_size + to_add ) ;
memcpy ( buffer - > data ( ) + cur_size , data , to_add ) ;
return to_add ;
}
2021-07-18 14:41:19 +00:00
static json api_request ( const std : : string & url , const std : : map < std : : string , std : : string > & params )
2020-11-22 15:17:14 +00:00
{
CURL * curl = curl_easy_init ( ) ;
curl_slist * headers = nullptr ;
for ( const auto & [ header , value ] : yt_config . extra_headers ) {
2021-07-18 14:41:19 +00:00
std : : string h = header ;
h . append ( " : " ) . append ( value ) ;
headers = curl_slist_append ( headers , h . c_str ( ) ) ;
2020-11-22 15:17:14 +00:00
}
CURLU * u = curl_url ( ) ;
curl_url_set ( u , CURLUPART_URL , url . c_str ( ) , 0 ) ;
for ( const auto & [ k , v ] : params ) {
2021-07-18 14:41:19 +00:00
std : : string p = k ;
p . append ( " = " ) . append ( v ) ;
2020-11-22 15:17:14 +00:00
curl_url_set ( u , CURLUPART_QUERY , p . c_str ( ) , CURLU_APPENDQUERY | CURLU_URLENCODE ) ;
}
char * real_url ;
curl_url_get ( u , CURLUPART_URL , & real_url , 0 ) ;
std : : vector < unsigned char > data ;
curl_easy_setopt ( curl , CURLOPT_URL , real_url ) ;
curl_easy_setopt ( curl , CURLOPT_HTTPHEADER , headers ) ;
curl_easy_setopt ( curl , CURLOPT_WRITEFUNCTION , curl_writecallback ) ;
curl_easy_setopt ( curl , CURLOPT_WRITEDATA , ( void * ) & data ) ;
//curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
curl_easy_perform ( curl ) ;
2020-11-22 23:03:59 +00:00
curl_free ( real_url ) ;
2020-11-22 15:17:14 +00:00
data . push_back ( 0 ) ;
2021-04-06 14:42:35 +00:00
long http_response = 0 ;
2020-11-22 15:17:14 +00:00
curl_easy_getinfo ( curl , CURLINFO_RESPONSE_CODE , & http_response ) ;
curl_url_cleanup ( u ) ;
curl_easy_cleanup ( curl ) ;
if ( headers )
curl_slist_free_all ( headers ) ;
try {
return json : : parse ( data ) ;
} catch ( json : : exception & err ) {
2020-12-02 20:11:44 +00:00
tui_abort ( " Failed to parse YouTube API response: \n %s " , err . what ( ) ) ;
2020-11-22 15:17:14 +00:00
}
return { } ;
}
2021-07-18 14:11:16 +00:00
Channel : : Channel ( sqlite3_stmt * row ) : id ( get_string ( row , 0 ) ) , name ( get_string ( row , 1 ) ) , is_virtual ( false ) ,
2021-07-18 21:57:44 +00:00
user_flags ( get_int ( row , 2 ) ) , unwatched ( 0 ) , tui_name_width ( 0 )
2020-11-22 15:17:14 +00:00
{
}
2021-07-18 14:11:16 +00:00
Channel : : Channel ( const std : : string & id , const std : : string & name ) : id ( id ) , name ( name ) , is_virtual ( false ) ,
2021-07-18 21:57:44 +00:00
user_flags ( 0 ) , unwatched ( 0 ) , tui_name_width ( 0 )
2020-11-22 15:17:14 +00:00
{
}
Channel Channel : : add ( sqlite3 * db , const std : : string & selector , const std : : string & value )
{
std : : map < std : : string , std : : string > params = {
{ " part " , " snippet " } ,
{ selector , value } ,
{ " key " , yt_config . api_key } ,
} ;
const json response = api_request ( " https://content.googleapis.com/youtube/v3/channels " , params ) ;
2020-12-02 21:55:49 +00:00
// Error responses dont have pageInfo items
if ( ! response . count ( " pageInfo " ) ) {
return Channel ( " " , " " ) ;
}
2020-11-22 15:17:14 +00:00
const json page_info = response [ " pageInfo " ] ;
const bool any_results = page_info [ " totalResults " ] > 0 ;
if ( ! any_results ) {
return Channel ( " " , " " ) ;
}
const std : : string channel_id = response [ " items " ] [ 0 ] [ " id " ] ;
const std : : string channel_name = response [ " items " ] [ 0 ] [ " snippet " ] [ " title " ] ;
sqlite3_stmt * query ;
2021-07-18 14:11:16 +00:00
SC ( sqlite3_prepare_v2 ( db , " INSERT INTO channels(channelId, name, user_flags) VALUES(?1, ?2, 0); " , - 1 , & query , nullptr ) ) ;
2020-11-22 15:17:14 +00:00
SC ( sqlite3_bind_text ( query , 1 , channel_id . c_str ( ) , - 1 , SQLITE_TRANSIENT ) ) ;
SC ( sqlite3_bind_text ( query , 2 , channel_name . c_str ( ) , - 1 , SQLITE_TRANSIENT ) ) ;
sqlite3_step ( query ) ;
SC ( sqlite3_finalize ( query ) ) ;
return Channel ( channel_id , channel_name ) ;
}
2021-07-21 22:03:20 +00:00
Channel Channel : : add_virtual ( const std : : string & name , const ChannelFilter & filter )
2020-11-26 12:27:11 +00:00
{
std : : string id = name ;
std : : transform ( id . begin ( ) , id . end ( ) , id . begin ( ) , [ ] ( char c ) { return std : : isalnum ( c ) ? std : : tolower ( c ) : ' - ' ; } ) ;
Channel channel ( " virtual- " + id , name ) ;
channel . is_virtual = true ;
2021-07-18 21:57:44 +00:00
channel . filter = filter ;
2020-11-26 12:27:11 +00:00
return channel ;
}
2020-11-22 20:29:45 +00:00
std : : vector < Channel > Channel : : get_all ( sqlite3 * db )
{
std : : vector < Channel > channels ;
sqlite3_stmt * query ;
SC ( sqlite3_prepare_v2 ( db , " SELECT * FROM channels; " , - 1 , & query , nullptr ) ) ;
while ( sqlite3_step ( query ) = = SQLITE_ROW ) {
channels . emplace_back ( query ) ;
}
SC ( sqlite3_finalize ( query ) ) ;
return channels ;
}
2020-11-22 15:17:14 +00:00
std : : string Channel : : upload_playlist ( ) const
{
return " UU " + id . substr ( 2 ) ;
}
bool video_is_known ( sqlite3 * db , const std : : string & channel_id , const std : : string & video_id )
{
sqlite3_stmt * query ;
SC ( sqlite3_prepare_v2 ( db , " SELECT 1 FROM videos WHERE channelId=?1 AND videoId=?2 LIMIT 1; " , - 1 , & query , nullptr ) ) ;
SC ( sqlite3_bind_text ( query , 1 , channel_id . c_str ( ) , - 1 , SQLITE_TRANSIENT ) ) ;
SC ( sqlite3_bind_text ( query , 2 , video_id . c_str ( ) , - 1 , SQLITE_TRANSIENT ) ) ;
bool known = false ;
const int res = sqlite3_step ( query ) ;
if ( res = = SQLITE_ROW ) {
known = sqlite3_column_int ( query , 0 ) > 0 ;
} else if ( res = = SQLITE_DONE ) {
known = false ;
}
SC ( sqlite3_finalize ( query ) ) ;
return known ;
}
2021-07-18 14:39:32 +00:00
void add_video ( sqlite3 * db , const json & snippet , const json & content_details , const std : : string & channel_id ) {
2020-11-22 15:17:14 +00:00
const std : : string video_id = snippet [ " resourceId " ] [ " videoId " ] ;
const std : : string title = snippet [ " title " ] ;
const std : : string description = snippet [ " description " ] ;
const int flags = 0 ;
2021-07-18 14:39:32 +00:00
const std : : string added_to_playlist = snippet [ " publishedAt " ] ;
const std : : string published = content_details [ " videoPublishedAt " ] ;
2020-11-22 15:17:14 +00:00
sqlite3_stmt * query ;
2021-07-18 14:39:32 +00:00
SC ( sqlite3_prepare_v2 ( db , " INSERT INTO videos (videoId, channelId, title, description, flags, added_to_playlist, published) values(?1,?2,?3,?4,?5,?6,?7); " , - 1 , & query , nullptr ) ) ;
2020-11-22 15:17:14 +00:00
SC ( sqlite3_bind_text ( query , 1 , video_id . c_str ( ) , - 1 , SQLITE_TRANSIENT ) ) ;
SC ( sqlite3_bind_text ( query , 2 , channel_id . c_str ( ) , - 1 , SQLITE_TRANSIENT ) ) ;
SC ( sqlite3_bind_text ( query , 3 , title . c_str ( ) , - 1 , SQLITE_TRANSIENT ) ) ;
SC ( sqlite3_bind_text ( query , 4 , description . c_str ( ) , - 1 , SQLITE_TRANSIENT ) ) ;
SC ( sqlite3_bind_int ( query , 5 , flags ) ) ;
2021-07-18 14:39:32 +00:00
SC ( sqlite3_bind_text ( query , 6 , added_to_playlist . c_str ( ) , - 1 , SQLITE_TRANSIENT ) ) ;
SC ( sqlite3_bind_text ( query , 7 , published . c_str ( ) , - 1 , SQLITE_TRANSIENT ) ) ;
2020-11-22 15:17:14 +00:00
sqlite3_step ( query ) ;
SC ( sqlite3_finalize ( query ) ) ;
}
2021-07-18 14:39:32 +00:00
int Channel : : fetch_new_videos ( sqlite3 * db , progress_info * info , std : : optional < std : : string > after , std : : optional < int > max_count ) const
2020-11-22 15:17:14 +00:00
{
const std : : string playlist_id = upload_playlist ( ) ;
std : : map < std : : string , std : : string > params = {
2021-07-18 14:39:32 +00:00
{ " part " , " snippet,contentDetails " } ,
2020-11-22 15:17:14 +00:00
{ " playlistId " , playlist_id } ,
{ " maxResults " , " 50 " } ,
{ " key " , yt_config . api_key } ,
} ;
2020-11-22 19:37:12 +00:00
db_transaction transaction ;
2020-11-22 15:17:14 +00:00
int processed = 0 ;
bool abort = false ;
while ( true ) {
const json response = api_request ( " https://content.googleapis.com/youtube/v3/playlistItems " , params ) ;
2020-12-04 08:01:15 +00:00
if ( response . empty ( ) | | ! response . count ( " pageInfo " ) ) // TODO: Better API error detection/handling. For now just break if there is no pageInfo field.
2020-11-22 15:17:14 +00:00
break ;
for ( auto & item : response [ " items " ] ) {
auto snippet = item [ " snippet " ] ;
2021-07-18 14:39:32 +00:00
auto content_details = item [ " contentDetails " ] ;
2020-11-22 15:17:14 +00:00
std : : string channel_id = snippet [ " channelId " ] ;
std : : string video_id = snippet [ " resourceId " ] [ " videoId " ] ;
std : : string title = snippet [ " title " ] ;
if ( after ) {
2021-07-18 14:39:32 +00:00
auto addedToPlaylistAt = snippet [ " publishedAt " ] ;
auto publishedAt = content_details [ " videoPublishedAt " ] ;
if ( addedToPlaylistAt < * after ) {
2020-11-22 15:17:14 +00:00
//fprintf(stderr, "Stopping at video '%s': Too old.\r\n", title.c_str());
abort = true ;
break ;
}
}
if ( video_is_known ( db , channel_id , video_id ) ) {
//fprintf(stderr, "Stopping at video '%s': Already known.\r\n", title.c_str());
abort = true ;
break ;
}
2021-07-18 14:39:32 +00:00
add_video ( db , snippet , content_details , channel_id ) ;
2020-11-22 15:17:14 +00:00
//fprintf(stderr, "New video: '%s': %s.\r\n", title.c_str(), video_id.c_str());
2020-12-01 19:37:08 +00:00
processed + + ;
2020-11-22 15:17:14 +00:00
if ( max_count & & processed > = * max_count ) {
abort = true ;
break ;
}
}
const json page_info = response [ " pageInfo " ] ;
const int results = page_info [ " totalResults " ] ;
if ( info )
update_progress ( info , processed , results ) ;
2020-11-22 22:44:33 +00:00
if ( ! abort & & response . count ( " nextPageToken " ) ) {
2020-11-22 15:17:14 +00:00
params [ " pageToken " ] = response [ " nextPageToken " ] ;
//fprintf(stderr, "Processed %d. Next page...\r\n", processed);
} else {
break ;
}
}
2020-12-01 19:37:08 +00:00
return processed ;
2020-11-22 15:17:14 +00:00
}
void Channel : : load_info ( sqlite3 * db )
{
2020-11-22 20:31:26 +00:00
unwatched = 0 ;
2020-11-26 12:27:11 +00:00
if ( is_virtual ) {
return ;
}
2020-11-22 15:17:14 +00:00
sqlite3_stmt * query ;
2020-11-22 20:31:26 +00:00
SC ( sqlite3_prepare_v2 ( db , " SELECT flags, count(*) as videos FROM videos where channelId = ?1 GROUP by flags; " , - 1 , & query , nullptr ) ) ;
2020-11-22 15:17:14 +00:00
SC ( sqlite3_bind_text ( query , 1 , id . c_str ( ) , - 1 , SQLITE_TRANSIENT ) ) ;
2020-11-22 20:31:26 +00:00
while ( sqlite3_step ( query ) = = SQLITE_ROW ) {
const int flags = sqlite3_column_int ( query , 0 ) ;
const int count = sqlite3_column_int ( query , 1 ) ;
if ( ( flags & kWatched ) = = 0 )
unwatched + = count ;
}
2020-11-22 15:17:14 +00:00
SC ( sqlite3_finalize ( query ) ) ;
}
bool Channel : : is_valid ( ) const
{
return ! id . empty ( ) & & ! name . empty ( ) ;
}
2021-07-18 21:58:33 +00:00
void Channel : : save_user_flags ( sqlite3 * db ) const
2021-07-18 14:11:16 +00:00
{
sqlite3_stmt * query ;
SC ( sqlite3_prepare_v2 ( db , " UPDATE channels SET user_flags = ?2 WHERE channelID = ?1; " , - 1 , & query , nullptr ) ) ;
SC ( sqlite3_bind_text ( query , 1 , id . c_str ( ) , - 1 , SQLITE_TRANSIENT ) ) ;
SC ( sqlite3_bind_int ( query , 2 , user_flags ) ) ;
SC ( sqlite3_step ( query ) ) ;
SC ( sqlite3_finalize ( query ) ) ;
}
2020-12-06 18:40:10 +00:00
Video : : Video ( sqlite3_stmt * row ) : id ( get_string ( row , 0 ) ) , channel_id ( get_string ( row , 1 ) ) , title ( get_string ( row , 2 ) ) ,
2021-07-18 14:39:32 +00:00
description ( get_string ( row , 3 ) ) , flags ( sqlite3_column_int ( row , 4 ) ) , added_to_playlist ( get_string ( row , 6 ) ) ,
published ( get_string ( row , 5 ) ) , tui_title_width ( 0 )
2020-11-22 15:17:14 +00:00
{
}
void Video : : set_flag ( sqlite3 * db , VideoFlag flag , bool value )
{
sqlite3_stmt * query ;
if ( value ) {
SC ( sqlite3_prepare_v2 ( db , " UPDATE videos SET flags = flags | ?1 WHERE videoID = ?2; " , - 1 , & query , nullptr ) ) ;
flags | = flag ;
} else {
SC ( sqlite3_prepare_v2 ( db , " UPDATE videos SET flags = flags & ~?1 WHERE videoID = ?2; " , - 1 , & query , nullptr ) ) ;
flags & = ~ flag ;
}
SC ( sqlite3_bind_int ( query , 1 , flag ) ) ;
SC ( sqlite3_bind_text ( query , 2 , id . c_str ( ) , - 1 , SQLITE_TRANSIENT ) ) ;
SC ( sqlite3_step ( query ) ) ;
SC ( sqlite3_finalize ( query ) ) ;
}
2020-11-22 20:29:45 +00:00
std : : vector < Video > Video : : get_all_for_channel ( const std : : string & channel_id )
{
std : : vector < Video > videos ;
sqlite3_stmt * query ;
2021-07-21 21:35:05 +00:00
SC ( sqlite3_prepare_v2 ( db , " SELECT * FROM videos WHERE channelId=?1 ORDER BY coalesce(published, added_to_playlist) DESC; " , - 1 , & query , nullptr ) ) ;
2020-11-22 20:29:45 +00:00
SC ( sqlite3_bind_text ( query , 1 , channel_id . c_str ( ) , - 1 , SQLITE_TRANSIENT ) ) ;
while ( sqlite3_step ( query ) = = SQLITE_ROW ) {
videos . emplace_back ( query ) ;
}
SC ( sqlite3_finalize ( query ) ) ;
return videos ;
}
2020-11-26 12:27:11 +00:00
2021-07-18 21:57:44 +00:00
std : : vector < Video > Video : : get_all_with_filter ( const ChannelFilter & filter )
2020-11-26 12:27:11 +00:00
{
std : : vector < Video > videos ;
sqlite3_stmt * query ;
2021-07-18 21:57:44 +00:00
SC ( sqlite3_prepare_v2 ( db , R " (SELECT videos.*, channels.user_flags
FROM videos JOIN channels ON videos . channelId = channels . channelId
WHERE videos . flags & ? 1 = ? 2
AND channels . user_flags & ? 3 = ? 4
2021-07-21 21:35:05 +00:00
ORDER BY coalesce ( published , added_to_playlist ) DESC ; ) " , -1, &query, nullptr));
2021-07-18 21:57:44 +00:00
SC ( sqlite3_bind_int ( query , 1 , filter . video_mask ) ) ;
SC ( sqlite3_bind_int ( query , 2 , filter . video_value ) ) ;
SC ( sqlite3_bind_int ( query , 3 , filter . user_mask ) ) ;
SC ( sqlite3_bind_int ( query , 4 , filter . user_value ) ) ;
2020-11-26 12:27:11 +00:00
while ( sqlite3_step ( query ) = = SQLITE_ROW ) {
videos . emplace_back ( query ) ;
}
SC ( sqlite3_finalize ( query ) ) ;
return videos ;
}
2021-07-18 21:57:44 +00:00
ChannelFilter : : ChannelFilter ( ) : id ( - 1 ) , name ( std : : string ( ) ) , video_mask ( 0 ) , video_value ( 0 ) , user_mask ( 0 ) , user_value ( 0 )
{
}
2021-07-21 22:03:20 +00:00
ChannelFilter : : ChannelFilter ( sqlite3_stmt * row ) : id ( get_int ( row , 0 ) ) , name ( get_string ( row , 1 ) ) ,
video_mask ( get_int ( row , 2 ) ) , video_value ( get_int ( row , 3 ) ) , user_mask ( get_int ( row , 4 ) ) , user_value ( get_int ( row , 5 ) )
{
}
2021-07-18 21:57:44 +00:00
ChannelFilter : : ChannelFilter ( const int id , const std : : string & name ) : id ( id ) , name ( name ) ,
video_mask ( 0 ) , video_value ( 0 ) , user_mask ( 0 ) , user_value ( 0 )
{
}
2021-07-21 22:03:20 +00:00
void ChannelFilter : : save ( sqlite3 * db ) const
{
if ( id < 0 )
return ;
sqlite3_stmt * query ;
SC ( sqlite3_prepare_v2 ( db , " UPDATE channel_filters SET name=?2, video_mask=?3, video_value=?4, user_mask=?5, user_value=?6 WHERE id = ?1; " , - 1 , & query , nullptr ) ) ;
SC ( sqlite3_bind_int ( query , 1 , id ) ) ;
SC ( sqlite3_bind_text ( query , 2 , name . c_str ( ) , - 1 , SQLITE_TRANSIENT ) ) ;
SC ( sqlite3_bind_int ( query , 3 , video_mask ) ) ;
SC ( sqlite3_bind_int ( query , 4 , video_value ) ) ;
SC ( sqlite3_bind_int ( query , 5 , user_mask ) ) ;
SC ( sqlite3_bind_int ( query , 6 , user_value ) ) ;
SC ( sqlite3_step ( query ) ) ;
SC ( sqlite3_finalize ( query ) ) ;
}
ChannelFilter ChannelFilter : : add ( sqlite3 * db , const std : : string & name )
{
sqlite3_stmt * query ;
SC ( sqlite3_prepare_v2 ( db , " INSERT INTO channel_filters(name) values(?1); " , - 1 , & query , nullptr ) ) ;
SC ( sqlite3_bind_text ( query , 1 , name . c_str ( ) , - 1 , nullptr ) ) ;
SC ( sqlite3_step ( query ) ) ;
SC ( sqlite3_finalize ( query ) ) ;
int id = sqlite3_last_insert_rowid ( db ) ;
return ChannelFilter ( id , name ) ;
}
std : : vector < ChannelFilter > ChannelFilter : : get_all ( sqlite3 * db )
{
std : : vector < ChannelFilter > result ;
sqlite3_stmt * query ;
SC ( sqlite3_prepare_v2 ( db , " SELECT * FROM channel_filters ORDER BY id; " , - 1 , & query , nullptr ) ) ;
while ( sqlite3_step ( query ) = = SQLITE_ROW ) {
result . emplace_back ( query ) ;
}
SC ( sqlite3_finalize ( query ) ) ;
return result ;
}