split out sync, search, scheduler & config

This commit is contained in:
Damien Elmes 2021-07-10 21:33:12 +10:00
parent 9e0a295ab9
commit 35b059ecdb
29 changed files with 675 additions and 617 deletions

View file

@ -7,7 +7,6 @@ package anki.backend;
import "anki/generic.proto";
import "anki/cards.proto";
import "anki/decks.proto";
import "anki/collection.proto";
import "anki/notes.proto";
import "anki/notetypes.proto";
@ -19,12 +18,12 @@ import "anki/notetypes.proto";
/// that information is not available in prost, so we define an enum to make
/// sure all clients agree on the service index
enum ServiceIndex {
SERVICE_INDEX_SCHEDULING = 0;
SERVICE_INDEX_SCHEDULER = 0;
SERVICE_INDEX_DECKS = 1;
SERVICE_INDEX_NOTES = 2;
SERVICE_INDEX_SYNC = 3;
SERVICE_INDEX_NOTETYPES = 4;
SERVICE_INDEX_CONFIG = 5;
SERVICE_INDEX_CONFIGS = 5;
SERVICE_INDEX_CARD_RENDERING = 6;
SERVICE_INDEX_DECK_CONFIG = 7;
SERVICE_INDEX_TAGS = 8;
@ -36,61 +35,6 @@ enum ServiceIndex {
SERVICE_INDEX_CARDS = 14;
}
service SchedulingService {
rpc SchedTimingToday(generic.Empty) returns (SchedTimingTodayResponse);
rpc StudiedToday(generic.Empty) returns (generic.String);
rpc StudiedTodayMessage(StudiedTodayMessageRequest) returns (generic.String);
rpc UpdateStats(UpdateStatsRequest) returns (generic.Empty);
rpc ExtendLimits(ExtendLimitsRequest) returns (generic.Empty);
rpc CountsForDeckToday(decks.DeckId) returns (CountsForDeckTodayResponse);
rpc CongratsInfo(generic.Empty) returns (CongratsInfoResponse);
rpc RestoreBuriedAndSuspendedCards(cards.CardIds)
returns (collection.OpChanges);
rpc UnburyDeck(UnburyDeckRequest) returns (collection.OpChanges);
rpc BuryOrSuspendCards(BuryOrSuspendCardsRequest)
returns (collection.OpChangesWithCount);
rpc EmptyFilteredDeck(decks.DeckId) returns (collection.OpChanges);
rpc RebuildFilteredDeck(decks.DeckId) returns (collection.OpChangesWithCount);
rpc ScheduleCardsAsNew(ScheduleCardsAsNewRequest)
returns (collection.OpChanges);
rpc SetDueDate(SetDueDateRequest) returns (collection.OpChanges);
rpc SortCards(SortCardsRequest) returns (collection.OpChangesWithCount);
rpc SortDeck(SortDeckRequest) returns (collection.OpChangesWithCount);
rpc GetNextCardStates(cards.CardId) returns (NextCardStates);
rpc DescribeNextStates(NextCardStates) returns (generic.StringList);
rpc StateIsLeech(SchedulingState) returns (generic.Bool);
rpc AnswerCard(CardAnswer) returns (collection.OpChanges);
rpc UpgradeScheduler(generic.Empty) returns (generic.Empty);
rpc GetQueuedCards(GetQueuedCardsRequest) returns (QueuedCards);
}
service SyncService {
rpc SyncMedia(SyncAuth) returns (generic.Empty);
rpc AbortSync(generic.Empty) returns (generic.Empty);
rpc AbortMediaSync(generic.Empty) returns (generic.Empty);
rpc BeforeUpload(generic.Empty) returns (generic.Empty);
rpc SyncLogin(SyncLoginRequest) returns (SyncAuth);
rpc SyncStatus(SyncAuth) returns (SyncStatusResponse);
rpc SyncCollection(SyncAuth) returns (SyncCollectionResponse);
rpc FullUpload(SyncAuth) returns (generic.Empty);
rpc FullDownload(SyncAuth) returns (generic.Empty);
rpc SyncServerMethod(SyncServerMethodRequest) returns (generic.Json);
}
service ConfigService {
rpc GetConfigJson(generic.String) returns (generic.Json);
rpc SetConfigJson(SetConfigJsonRequest) returns (collection.OpChanges);
rpc SetConfigJsonNoUndo(SetConfigJsonRequest) returns (generic.Empty);
rpc RemoveConfig(generic.String) returns (collection.OpChanges);
rpc GetAllConfig(generic.Empty) returns (generic.Json);
rpc GetConfigBool(Config.Bool) returns (generic.Bool);
rpc SetConfigBool(SetConfigBoolRequest) returns (collection.OpChanges);
rpc GetConfigString(Config.String) returns (generic.String);
rpc SetConfigString(SetConfigStringRequest) returns (collection.OpChanges);
rpc GetPreferences(generic.Empty) returns (Preferences);
rpc SetPreferences(Preferences) returns (collection.OpChanges);
}
service CardRenderingService {
rpc ExtractAVTags(ExtractAVTagsRequest) returns (ExtractAVTagsResponse);
rpc ExtractLatex(ExtractLatexRequest) returns (ExtractLatexResponse);
@ -121,19 +65,6 @@ service TagsService {
returns (collection.OpChangesWithCount);
}
service SearchService {
rpc BuildSearchString(SearchNode) returns (generic.String);
rpc SearchCards(SearchRequest) returns (SearchResponse);
rpc SearchNotes(SearchRequest) returns (SearchResponse);
rpc JoinSearchNodes(JoinSearchNodesRequest) returns (generic.String);
rpc ReplaceSearchNode(ReplaceSearchNodeRequest) returns (generic.String);
rpc FindAndReplace(FindAndReplaceRequest)
returns (collection.OpChangesWithCount);
rpc AllBrowserColumns(generic.Empty) returns (BrowserColumns);
rpc BrowserRowForId(generic.Int64) returns (BrowserRow);
rpc SetActiveBrowserColumns(generic.StringList) returns (generic.Empty);
}
service StatsService {
rpc CardStats(cards.CardId) returns (generic.String);
rpc Graphs(GraphsRequest) returns (GraphsResponse);
@ -194,11 +125,6 @@ message BackendError {
// Messages
///////////////////////////////////////////////////////////
message SchedTimingTodayResponse {
uint32 days_elapsed = 1;
int64 next_day_at = 2;
}
message RenderExistingCardRequest {
int64 card_id = 1;
bool browser = 2;
@ -295,118 +221,11 @@ message TrashMediaFilesRequest {
repeated string fnames = 1;
}
message StudiedTodayMessageRequest {
uint32 cards = 1;
double seconds = 2;
}
message CongratsLearnMessageRequest {
float next_due = 1;
uint32 remaining = 2;
}
message SearchRequest {
string search = 1;
SortOrder order = 2;
}
message SearchResponse {
repeated int64 ids = 1;
}
message SortOrder {
message Builtin {
string column = 1;
bool reverse = 2;
}
oneof value {
generic.Empty none = 1;
string custom = 2;
Builtin builtin = 3;
}
}
message SearchNode {
message Dupe {
int64 notetype_id = 1;
string first_field = 2;
}
enum Flag {
FLAG_NONE = 0;
FLAG_ANY = 1;
FLAG_RED = 2;
FLAG_ORANGE = 3;
FLAG_GREEN = 4;
FLAG_BLUE = 5;
FLAG_PINK = 6;
FLAG_TURQUOISE = 7;
FLAG_PURPLE = 8;
}
enum Rating {
RATING_ANY = 0;
RATING_AGAIN = 1;
RATING_HARD = 2;
RATING_GOOD = 3;
RATING_EASY = 4;
RATING_BY_RESCHEDULE = 5;
}
message Rated {
uint32 days = 1;
Rating rating = 2;
}
enum CardState {
CARD_STATE_NEW = 0;
CARD_STATE_LEARN = 1;
CARD_STATE_REVIEW = 2;
CARD_STATE_DUE = 3;
CARD_STATE_SUSPENDED = 4;
CARD_STATE_BURIED = 5;
}
message IdList {
repeated int64 ids = 1;
}
message Group {
enum Joiner {
AND = 0;
OR = 1;
}
repeated SearchNode nodes = 1;
Joiner joiner = 2;
}
oneof filter {
Group group = 1;
SearchNode negated = 2;
string parsable_text = 3;
uint32 template = 4;
int64 nid = 5;
Dupe dupe = 6;
string field_name = 7;
Rated rated = 8;
uint32 added_in_days = 9;
int32 due_in_days = 10;
Flag flag = 11;
CardState card_state = 12;
IdList nids = 13;
uint32 edited_in_days = 14;
string deck = 15;
int32 due_on_day = 16;
string tag = 17;
string note = 18;
uint32 introduced_in_days = 19;
}
}
message JoinSearchNodesRequest {
SearchNode.Group.Joiner joiner = 1;
SearchNode existing_node = 2;
SearchNode additional_node = 3;
}
message ReplaceSearchNodeRequest {
SearchNode existing_node = 1;
SearchNode replacement_node = 2;
}
message SetTagCollapsedRequest {
string name = 1;
bool collapsed = 2;
@ -433,12 +252,6 @@ message RenameTagsRequest {
string new_prefix = 2;
}
message SetConfigJsonRequest {
string key = 1;
bytes value_json = 2;
bool undoable = 3;
}
message EmptyCardsReport {
message NoteWithEmptyCards {
int64 note_id = 1;
@ -449,59 +262,6 @@ message EmptyCardsReport {
repeated NoteWithEmptyCards notes = 2;
}
message FindAndReplaceRequest {
repeated int64 nids = 1;
string search = 2;
string replacement = 3;
bool regex = 4;
bool match_case = 5;
string field_name = 6;
}
message BrowserColumns {
enum Sorting {
SORTING_NONE = 0;
SORTING_NORMAL = 1;
SORTING_REVERSED = 2;
}
enum Alignment {
ALIGNMENT_START = 0;
ALIGNMENT_CENTER = 1;
}
message Column {
string key = 1;
string cards_mode_label = 2;
string notes_mode_label = 3;
Sorting sorting = 4;
bool uses_cell_font = 5;
Alignment alignment = 6;
}
repeated Column columns = 1;
}
message BrowserRow {
message Cell {
string text = 1;
bool is_rtl = 2;
}
enum Color {
COLOR_DEFAULT = 0;
COLOR_MARKED = 1;
COLOR_SUSPENDED = 2;
COLOR_FLAG_RED = 3;
COLOR_FLAG_ORANGE = 4;
COLOR_FLAG_GREEN = 5;
COLOR_FLAG_BLUE = 6;
COLOR_FLAG_PINK = 7;
COLOR_FLAG_TURQUOISE = 8;
COLOR_FLAG_PURPLE = 9;
}
repeated Cell cells = 1;
Color color = 2;
string font_name = 3;
uint32 font_size = 4;
}
message NoteIdsAndTagsRequest {
repeated int64 note_ids = 1;
string tags = 2;
@ -515,117 +275,6 @@ message FindAndReplaceTagRequest {
bool match_case = 5;
}
message Preferences {
message Scheduling {
enum NewReviewMix {
DISTRIBUTE = 0;
REVIEWS_FIRST = 1;
NEW_FIRST = 2;
}
// read only; 1-3
uint32 scheduler_version = 1;
uint32 rollover = 2;
uint32 learn_ahead_secs = 3;
NewReviewMix new_review_mix = 4;
// v2 only
bool new_timezone = 5;
bool day_learn_first = 6;
}
message Reviewing {
bool hide_audio_play_buttons = 1;
bool interrupt_audio_when_answering = 2;
bool show_remaining_due_counts = 3;
bool show_intervals_on_buttons = 4;
uint32 time_limit_secs = 5;
}
message Editing {
bool adding_defaults_to_current_deck = 1;
bool paste_images_as_png = 2;
bool paste_strips_formatting = 3;
string default_search_text = 4;
}
Scheduling scheduling = 1;
Reviewing reviewing = 2;
Editing editing = 3;
}
message SyncLoginRequest {
string username = 1;
string password = 2;
}
message SyncStatusResponse {
enum Required {
NO_CHANGES = 0;
NORMAL_SYNC = 1;
FULL_SYNC = 2;
}
Required required = 1;
}
message SyncCollectionResponse {
enum ChangesRequired {
NO_CHANGES = 0;
NORMAL_SYNC = 1;
FULL_SYNC = 2;
// local collection has no cards; upload not an option
FULL_DOWNLOAD = 3;
// remote collection has no cards; download not an option
FULL_UPLOAD = 4;
}
uint32 host_number = 1;
string server_message = 2;
ChangesRequired required = 3;
}
message SyncAuth {
string hkey = 1;
uint32 host_number = 2;
}
message SyncServerMethodRequest {
enum Method {
HOST_KEY = 0;
META = 1;
START = 2;
APPLY_GRAVES = 3;
APPLY_CHANGES = 4;
CHUNK = 5;
APPLY_CHUNK = 6;
SANITY_CHECK = 7;
FINISH = 8;
ABORT = 9;
// caller must reopen after these two are called
FULL_UPLOAD = 10;
FULL_DOWNLOAD = 11;
}
Method method = 1;
bytes data = 2;
}
message UpdateStatsRequest {
int64 deck_id = 1;
int32 new_delta = 2;
int32 review_delta = 4;
int32 millisecond_delta = 5;
}
message ExtendLimitsRequest {
int64 deck_id = 1;
int32 new_delta = 2;
int32 review_delta = 3;
}
message CountsForDeckTodayResponse {
int32 new = 1;
int32 review = 2;
}
message GraphsRequest {
string search = 1;
uint32 days = 2;
@ -673,205 +322,7 @@ message RevlogEntry {
uint32 taken_millis = 8;
ReviewKind review_kind = 9;
}
message CongratsInfoResponse {
uint32 learn_remaining = 1;
uint32 secs_until_next_learn = 2;
bool review_remaining = 3;
bool new_remaining = 4;
bool have_sched_buried = 5;
bool have_user_buried = 6;
bool is_filtered_deck = 7;
bool bridge_commands_supported = 8;
string deck_description = 9;
}
message UnburyDeckRequest {
enum Mode {
ALL = 0;
SCHED_ONLY = 1;
USER_ONLY = 2;
}
int64 deck_id = 1;
Mode mode = 2;
}
message BuryOrSuspendCardsRequest {
enum Mode {
SUSPEND = 0;
BURY_SCHED = 1;
BURY_USER = 2;
}
repeated int64 card_ids = 1;
repeated int64 note_ids = 2;
Mode mode = 3;
}
message ScheduleCardsAsNewRequest {
repeated int64 card_ids = 1;
bool log = 2;
}
message SetDueDateRequest {
repeated int64 card_ids = 1;
string days = 2;
Config.String config_key = 3;
}
message SortCardsRequest {
repeated int64 card_ids = 1;
uint32 starting_from = 2;
uint32 step_size = 3;
bool randomize = 4;
bool shift_existing = 5;
}
message SortDeckRequest {
int64 deck_id = 1;
bool randomize = 2;
}
message Config {
message Bool {
enum Key {
BROWSER_TABLE_SHOW_NOTES_MODE = 0;
PREVIEW_BOTH_SIDES = 3;
COLLAPSE_TAGS = 4;
COLLAPSE_NOTETYPES = 5;
COLLAPSE_DECKS = 6;
COLLAPSE_SAVED_SEARCHES = 7;
COLLAPSE_TODAY = 8;
COLLAPSE_CARD_STATE = 9;
COLLAPSE_FLAGS = 10;
SCHED_2021 = 11;
ADDING_DEFAULTS_TO_CURRENT_DECK = 12;
HIDE_AUDIO_PLAY_BUTTONS = 13;
INTERRUPT_AUDIO_WHEN_ANSWERING = 14;
PASTE_IMAGES_AS_PNG = 15;
PASTE_STRIPS_FORMATTING = 16;
NORMALIZE_NOTE_TEXT = 17;
}
Key key = 1;
}
message String {
enum Key {
SET_DUE_BROWSER = 0;
SET_DUE_REVIEWER = 1;
DEFAULT_SEARCH_TEXT = 2;
CARD_STATE_CUSTOMIZER = 3;
}
Key key = 1;
}
}
message SetConfigBoolRequest {
Config.Bool.Key key = 1;
bool value = 2;
bool undoable = 3;
}
message SetConfigStringRequest {
Config.String.Key key = 1;
string value = 2;
bool undoable = 3;
}
message RenderMarkdownRequest {
string markdown = 1;
bool sanitize = 2;
}
message SchedulingState {
message New {
uint32 position = 1;
}
message Learning {
uint32 remaining_steps = 1;
uint32 scheduled_secs = 2;
}
message Review {
uint32 scheduled_days = 1;
uint32 elapsed_days = 2;
float ease_factor = 3;
uint32 lapses = 4;
bool leeched = 5;
}
message Relearning {
Review review = 1;
Learning learning = 2;
}
message Normal {
oneof value {
New new = 1;
Learning learning = 2;
Review review = 3;
Relearning relearning = 4;
}
}
message Preview {
uint32 scheduled_secs = 1;
bool finished = 2;
}
message ReschedulingFilter {
Normal original_state = 1;
}
message Filtered {
oneof value {
Preview preview = 1;
ReschedulingFilter rescheduling = 2;
}
}
oneof value {
Normal normal = 1;
Filtered filtered = 2;
}
}
message NextCardStates {
SchedulingState current = 1;
SchedulingState again = 2;
SchedulingState hard = 3;
SchedulingState good = 4;
SchedulingState easy = 5;
}
message CardAnswer {
enum Rating {
AGAIN = 0;
HARD = 1;
GOOD = 2;
EASY = 3;
}
int64 card_id = 1;
SchedulingState current_state = 2;
SchedulingState new_state = 3;
Rating rating = 4;
int64 answered_at_millis = 5;
uint32 milliseconds_taken = 6;
}
message GetQueuedCardsRequest {
uint32 fetch_limit = 1;
bool intraday_learning_only = 2;
}
message QueuedCards {
enum Queue {
NEW = 0;
LEARNING = 1;
REVIEW = 2;
}
message QueuedCard {
cards.Card card = 1;
Queue queue = 2;
NextCardStates next_states = 3;
}
repeated QueuedCard cards = 1;
uint32 new_count = 2;
uint32 learning_count = 3;
uint32 review_count = 4;
}

113
proto/anki/configs.proto Normal file
View file

@ -0,0 +1,113 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
syntax = "proto3";
package anki.configs;
import "anki/generic.proto";
import "anki/collection.proto";
service ConfigsService {
rpc GetConfigJson(generic.String) returns (generic.Json);
rpc SetConfigJson(SetConfigJsonRequest) returns (collection.OpChanges);
rpc SetConfigJsonNoUndo(SetConfigJsonRequest) returns (generic.Empty);
rpc RemoveConfig(generic.String) returns (collection.OpChanges);
rpc GetAllConfig(generic.Empty) returns (generic.Json);
rpc GetConfigBool(Config.Bool) returns (generic.Bool);
rpc SetConfigBool(SetConfigBoolRequest) returns (collection.OpChanges);
rpc GetConfigString(Config.String) returns (generic.String);
rpc SetConfigString(SetConfigStringRequest) returns (collection.OpChanges);
rpc GetPreferences(generic.Empty) returns (Preferences);
rpc SetPreferences(Preferences) returns (collection.OpChanges);
}
message Config {
message Bool {
enum Key {
BROWSER_TABLE_SHOW_NOTES_MODE = 0;
PREVIEW_BOTH_SIDES = 3;
COLLAPSE_TAGS = 4;
COLLAPSE_NOTETYPES = 5;
COLLAPSE_DECKS = 6;
COLLAPSE_SAVED_SEARCHES = 7;
COLLAPSE_TODAY = 8;
COLLAPSE_CARD_STATE = 9;
COLLAPSE_FLAGS = 10;
SCHED_2021 = 11;
ADDING_DEFAULTS_TO_CURRENT_DECK = 12;
HIDE_AUDIO_PLAY_BUTTONS = 13;
INTERRUPT_AUDIO_WHEN_ANSWERING = 14;
PASTE_IMAGES_AS_PNG = 15;
PASTE_STRIPS_FORMATTING = 16;
NORMALIZE_NOTE_TEXT = 17;
}
Key key = 1;
}
message String {
enum Key {
SET_DUE_BROWSER = 0;
SET_DUE_REVIEWER = 1;
DEFAULT_SEARCH_TEXT = 2;
CARD_STATE_CUSTOMIZER = 3;
}
Key key = 1;
}
}
message SetConfigBoolRequest {
Config.Bool.Key key = 1;
bool value = 2;
bool undoable = 3;
}
message SetConfigStringRequest {
Config.String.Key key = 1;
string value = 2;
bool undoable = 3;
}
message SetConfigJsonRequest {
string key = 1;
bytes value_json = 2;
bool undoable = 3;
}
message Preferences {
message Scheduling {
enum NewReviewMix {
DISTRIBUTE = 0;
REVIEWS_FIRST = 1;
NEW_FIRST = 2;
}
// read only; 1-3
uint32 scheduler_version = 1;
uint32 rollover = 2;
uint32 learn_ahead_secs = 3;
NewReviewMix new_review_mix = 4;
// v2 only
bool new_timezone = 5;
bool day_learn_first = 6;
}
message Reviewing {
bool hide_audio_play_buttons = 1;
bool interrupt_audio_when_answering = 2;
bool show_remaining_due_counts = 3;
bool show_intervals_on_buttons = 4;
uint32 time_limit_secs = 5;
}
message Editing {
bool adding_defaults_to_current_deck = 1;
bool paste_images_as_png = 2;
bool paste_strips_formatting = 3;
string default_search_text = 4;
}
Scheduling scheduling = 1;
Reviewing reviewing = 2;
Editing editing = 3;
}

219
proto/anki/scheduler.proto Normal file
View file

@ -0,0 +1,219 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
syntax = "proto3";
package anki.scheduler;
import "anki/generic.proto";
import "anki/cards.proto";
import "anki/decks.proto";
import "anki/collection.proto";
import "anki/configs.proto";
service SchedulerService {
rpc GetQueuedCards(GetQueuedCardsRequest) returns (QueuedCards);
rpc AnswerCard(CardAnswer) returns (collection.OpChanges);
rpc SchedTimingToday(generic.Empty) returns (SchedTimingTodayResponse);
rpc StudiedToday(generic.Empty) returns (generic.String);
rpc StudiedTodayMessage(StudiedTodayMessageRequest) returns (generic.String);
rpc UpdateStats(UpdateStatsRequest) returns (generic.Empty);
rpc ExtendLimits(ExtendLimitsRequest) returns (generic.Empty);
rpc CountsForDeckToday(decks.DeckId) returns (CountsForDeckTodayResponse);
rpc CongratsInfo(generic.Empty) returns (CongratsInfoResponse);
rpc RestoreBuriedAndSuspendedCards(cards.CardIds)
returns (collection.OpChanges);
rpc UnburyDeck(UnburyDeckRequest) returns (collection.OpChanges);
rpc BuryOrSuspendCards(BuryOrSuspendCardsRequest)
returns (collection.OpChangesWithCount);
rpc EmptyFilteredDeck(decks.DeckId) returns (collection.OpChanges);
rpc RebuildFilteredDeck(decks.DeckId) returns (collection.OpChangesWithCount);
rpc ScheduleCardsAsNew(ScheduleCardsAsNewRequest)
returns (collection.OpChanges);
rpc SetDueDate(SetDueDateRequest) returns (collection.OpChanges);
rpc SortCards(SortCardsRequest) returns (collection.OpChangesWithCount);
rpc SortDeck(SortDeckRequest) returns (collection.OpChangesWithCount);
rpc GetNextCardStates(cards.CardId) returns (NextCardStates);
rpc DescribeNextStates(NextCardStates) returns (generic.StringList);
rpc StateIsLeech(SchedulingState) returns (generic.Bool);
rpc UpgradeScheduler(generic.Empty) returns (generic.Empty);
}
message SchedulingState {
message New {
uint32 position = 1;
}
message Learning {
uint32 remaining_steps = 1;
uint32 scheduled_secs = 2;
}
message Review {
uint32 scheduled_days = 1;
uint32 elapsed_days = 2;
float ease_factor = 3;
uint32 lapses = 4;
bool leeched = 5;
}
message Relearning {
Review review = 1;
Learning learning = 2;
}
message Normal {
oneof value {
New new = 1;
Learning learning = 2;
Review review = 3;
Relearning relearning = 4;
}
}
message Preview {
uint32 scheduled_secs = 1;
bool finished = 2;
}
message ReschedulingFilter {
Normal original_state = 1;
}
message Filtered {
oneof value {
Preview preview = 1;
ReschedulingFilter rescheduling = 2;
}
}
oneof value {
Normal normal = 1;
Filtered filtered = 2;
}
}
message QueuedCards {
enum Queue {
NEW = 0;
LEARNING = 1;
REVIEW = 2;
}
message QueuedCard {
cards.Card card = 1;
Queue queue = 2;
NextCardStates next_states = 3;
}
repeated QueuedCard cards = 1;
uint32 new_count = 2;
uint32 learning_count = 3;
uint32 review_count = 4;
}
message GetQueuedCardsRequest {
uint32 fetch_limit = 1;
bool intraday_learning_only = 2;
}
message SchedTimingTodayResponse {
uint32 days_elapsed = 1;
int64 next_day_at = 2;
}
message StudiedTodayMessageRequest {
uint32 cards = 1;
double seconds = 2;
}
message UpdateStatsRequest {
int64 deck_id = 1;
int32 new_delta = 2;
int32 review_delta = 4;
int32 millisecond_delta = 5;
}
message ExtendLimitsRequest {
int64 deck_id = 1;
int32 new_delta = 2;
int32 review_delta = 3;
}
message CountsForDeckTodayResponse {
int32 new = 1;
int32 review = 2;
}
message CongratsInfoResponse {
uint32 learn_remaining = 1;
uint32 secs_until_next_learn = 2;
bool review_remaining = 3;
bool new_remaining = 4;
bool have_sched_buried = 5;
bool have_user_buried = 6;
bool is_filtered_deck = 7;
bool bridge_commands_supported = 8;
string deck_description = 9;
}
message UnburyDeckRequest {
enum Mode {
ALL = 0;
SCHED_ONLY = 1;
USER_ONLY = 2;
}
int64 deck_id = 1;
Mode mode = 2;
}
message BuryOrSuspendCardsRequest {
enum Mode {
SUSPEND = 0;
BURY_SCHED = 1;
BURY_USER = 2;
}
repeated int64 card_ids = 1;
repeated int64 note_ids = 2;
Mode mode = 3;
}
message ScheduleCardsAsNewRequest {
repeated int64 card_ids = 1;
bool log = 2;
}
message SetDueDateRequest {
repeated int64 card_ids = 1;
string days = 2;
configs.Config.String config_key = 3;
}
message SortCardsRequest {
repeated int64 card_ids = 1;
uint32 starting_from = 2;
uint32 step_size = 3;
bool randomize = 4;
bool shift_existing = 5;
}
message SortDeckRequest {
int64 deck_id = 1;
bool randomize = 2;
}
message NextCardStates {
SchedulingState current = 1;
SchedulingState again = 2;
SchedulingState hard = 3;
SchedulingState good = 4;
SchedulingState easy = 5;
}
message CardAnswer {
enum Rating {
AGAIN = 0;
HARD = 1;
GOOD = 2;
EASY = 3;
}
int64 card_id = 1;
SchedulingState current_state = 2;
SchedulingState new_state = 3;
Rating rating = 4;
int64 answered_at_millis = 5;
uint32 milliseconds_taken = 6;
}

177
proto/anki/search.proto Normal file
View file

@ -0,0 +1,177 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
syntax = "proto3";
package anki.search;
import "anki/generic.proto";
import "anki/collection.proto";
service SearchService {
rpc BuildSearchString(SearchNode) returns (generic.String);
rpc SearchCards(SearchRequest) returns (SearchResponse);
rpc SearchNotes(SearchRequest) returns (SearchResponse);
rpc JoinSearchNodes(JoinSearchNodesRequest) returns (generic.String);
rpc ReplaceSearchNode(ReplaceSearchNodeRequest) returns (generic.String);
rpc FindAndReplace(FindAndReplaceRequest)
returns (collection.OpChangesWithCount);
rpc AllBrowserColumns(generic.Empty) returns (BrowserColumns);
rpc BrowserRowForId(generic.Int64) returns (BrowserRow);
rpc SetActiveBrowserColumns(generic.StringList) returns (generic.Empty);
}
message SearchNode {
message Dupe {
int64 notetype_id = 1;
string first_field = 2;
}
enum Flag {
FLAG_NONE = 0;
FLAG_ANY = 1;
FLAG_RED = 2;
FLAG_ORANGE = 3;
FLAG_GREEN = 4;
FLAG_BLUE = 5;
FLAG_PINK = 6;
FLAG_TURQUOISE = 7;
FLAG_PURPLE = 8;
}
enum Rating {
RATING_ANY = 0;
RATING_AGAIN = 1;
RATING_HARD = 2;
RATING_GOOD = 3;
RATING_EASY = 4;
RATING_BY_RESCHEDULE = 5;
}
message Rated {
uint32 days = 1;
Rating rating = 2;
}
enum CardState {
CARD_STATE_NEW = 0;
CARD_STATE_LEARN = 1;
CARD_STATE_REVIEW = 2;
CARD_STATE_DUE = 3;
CARD_STATE_SUSPENDED = 4;
CARD_STATE_BURIED = 5;
}
message IdList {
repeated int64 ids = 1;
}
message Group {
enum Joiner {
AND = 0;
OR = 1;
}
repeated SearchNode nodes = 1;
Joiner joiner = 2;
}
oneof filter {
Group group = 1;
SearchNode negated = 2;
string parsable_text = 3;
uint32 template = 4;
int64 nid = 5;
Dupe dupe = 6;
string field_name = 7;
Rated rated = 8;
uint32 added_in_days = 9;
int32 due_in_days = 10;
Flag flag = 11;
CardState card_state = 12;
IdList nids = 13;
uint32 edited_in_days = 14;
string deck = 15;
int32 due_on_day = 16;
string tag = 17;
string note = 18;
uint32 introduced_in_days = 19;
}
}
message SearchRequest {
string search = 1;
SortOrder order = 2;
}
message SearchResponse {
repeated int64 ids = 1;
}
message SortOrder {
message Builtin {
string column = 1;
bool reverse = 2;
}
oneof value {
generic.Empty none = 1;
string custom = 2;
Builtin builtin = 3;
}
}
message JoinSearchNodesRequest {
SearchNode.Group.Joiner joiner = 1;
SearchNode existing_node = 2;
SearchNode additional_node = 3;
}
message ReplaceSearchNodeRequest {
SearchNode existing_node = 1;
SearchNode replacement_node = 2;
}
message FindAndReplaceRequest {
repeated int64 nids = 1;
string search = 2;
string replacement = 3;
bool regex = 4;
bool match_case = 5;
string field_name = 6;
}
message BrowserColumns {
enum Sorting {
SORTING_NONE = 0;
SORTING_NORMAL = 1;
SORTING_REVERSED = 2;
}
enum Alignment {
ALIGNMENT_START = 0;
ALIGNMENT_CENTER = 1;
}
message Column {
string key = 1;
string cards_mode_label = 2;
string notes_mode_label = 3;
Sorting sorting = 4;
bool uses_cell_font = 5;
Alignment alignment = 6;
}
repeated Column columns = 1;
}
message BrowserRow {
message Cell {
string text = 1;
bool is_rtl = 2;
}
enum Color {
COLOR_DEFAULT = 0;
COLOR_MARKED = 1;
COLOR_SUSPENDED = 2;
COLOR_FLAG_RED = 3;
COLOR_FLAG_ORANGE = 4;
COLOR_FLAG_GREEN = 5;
COLOR_FLAG_BLUE = 6;
COLOR_FLAG_PINK = 7;
COLOR_FLAG_TURQUOISE = 8;
COLOR_FLAG_PURPLE = 9;
}
repeated Cell cells = 1;
Color color = 2;
string font_name = 3;
uint32 font_size = 4;
}

76
proto/anki/sync.proto Normal file
View file

@ -0,0 +1,76 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
syntax = "proto3";
package anki.sync;
import "anki/generic.proto";
service SyncService {
rpc SyncMedia(SyncAuth) returns (generic.Empty);
rpc AbortSync(generic.Empty) returns (generic.Empty);
rpc AbortMediaSync(generic.Empty) returns (generic.Empty);
rpc BeforeUpload(generic.Empty) returns (generic.Empty);
rpc SyncLogin(SyncLoginRequest) returns (SyncAuth);
rpc SyncStatus(SyncAuth) returns (SyncStatusResponse);
rpc SyncCollection(SyncAuth) returns (SyncCollectionResponse);
rpc FullUpload(SyncAuth) returns (generic.Empty);
rpc FullDownload(SyncAuth) returns (generic.Empty);
rpc SyncServerMethod(SyncServerMethodRequest) returns (generic.Json);
}
message SyncAuth {
string hkey = 1;
uint32 host_number = 2;
}
message SyncLoginRequest {
string username = 1;
string password = 2;
}
message SyncStatusResponse {
enum Required {
NO_CHANGES = 0;
NORMAL_SYNC = 1;
FULL_SYNC = 2;
}
Required required = 1;
}
message SyncCollectionResponse {
enum ChangesRequired {
NO_CHANGES = 0;
NORMAL_SYNC = 1;
FULL_SYNC = 2;
// local collection has no cards; upload not an option
FULL_DOWNLOAD = 3;
// remote collection has no cards; download not an option
FULL_UPLOAD = 4;
}
uint32 host_number = 1;
string server_message = 2;
ChangesRequired required = 3;
}
message SyncServerMethodRequest {
enum Method {
HOST_KEY = 0;
META = 1;
START = 2;
APPLY_GRAVES = 3;
APPLY_CHANGES = 4;
CHUNK = 5;
APPLY_CHUNK = 6;
SANITY_CHECK = 7;
FINISH = 8;
ABORT = 9;
// caller must reopen after these two are called
FULL_UPLOAD = 10;
FULL_DOWNLOAD = 11;
}
Method method = 1;
bytes data = 2;
}

View file

@ -16,6 +16,10 @@ import anki.decks_pb2
import anki.deckconfig_pb2
import anki.notes_pb2
import anki.notetypes_pb2
import anki.scheduler_pb2
import anki.sync_pb2
import anki.configs_pb2
import anki.search_pb2
import stringcase
@ -179,6 +183,10 @@ service_modules = dict(
DECKS=anki.decks_pb2,
DECK_CONFIG=anki.deckconfig_pb2,
NOTETYPES=anki.notetypes_pb2,
SCHEDULER=anki.scheduler_pb2,
SYNC=anki.sync_pb2,
CONFIGS=anki.configs_pb2,
SEARCH=anki.search_pb2,
)
for service in anki.backend_pb2.ServiceIndex.DESCRIPTOR.values:

View file

@ -8,22 +8,22 @@ from __future__ import annotations
from typing import Any, Generator, List, Literal, Optional, Sequence, Tuple, Union, cast
import anki.backend_pb2 as _pb
from anki import collection_pb2
from anki import collection_pb2, configs_pb2, generic_pb2, search_pb2
from anki._legacy import DeprecatedNamesMixin, deprecated
# protobuf we publicly export - listed first to avoid circular imports
SearchNode = _pb.SearchNode
SearchNode = search_pb2.SearchNode
Progress = collection_pb2.Progress
EmptyCardsReport = _pb.EmptyCardsReport
GraphPreferences = _pb.GraphPreferences
Preferences = _pb.Preferences
Preferences = configs_pb2.Preferences
UndoStatus = collection_pb2.UndoStatus
OpChanges = collection_pb2.OpChanges
OpChangesWithCount = collection_pb2.OpChangesWithCount
OpChangesWithId = collection_pb2.OpChangesWithId
OpChangesAfterUndo = collection_pb2.OpChangesAfterUndo
BrowserRow = _pb.BrowserRow
BrowserColumns = _pb.BrowserColumns
BrowserRow = search_pb2.BrowserRow
BrowserColumns = search_pb2.BrowserColumns
import copy
import os
@ -34,7 +34,7 @@ import weakref
from dataclasses import dataclass, field
import anki.latex
from anki import generic_pb2, hooks
from anki import hooks
from anki._backend import RustBackend, Translations
from anki.browser import BrowserConfig, BrowserDefaults
from anki.cards import Card, CardId
@ -486,12 +486,12 @@ class Collection(DeprecatedNamesMixin):
order: Union[bool, str, BrowserColumns.Column],
reverse: bool,
finding_notes: bool,
) -> _pb.SortOrder:
) -> search_pb2.SortOrder:
if isinstance(order, str):
return _pb.SortOrder(custom=order)
return search_pb2.SortOrder(custom=order)
if isinstance(order, bool):
if order is False:
return _pb.SortOrder(none=generic_pb2.Empty())
return search_pb2.SortOrder(none=generic_pb2.Empty())
# order=True: set args to sort column and reverse from config
sort_key = BrowserConfig.sort_column_key(finding_notes)
order = self.get_browser_column(self.get_config(sort_key))
@ -499,13 +499,15 @@ class Collection(DeprecatedNamesMixin):
reverse = self.get_config(reverse_key)
if isinstance(order, BrowserColumns.Column):
if order.sorting != BrowserColumns.SORTING_NONE:
return _pb.SortOrder(
builtin=_pb.SortOrder.Builtin(column=order.key, reverse=reverse)
return search_pb2.SortOrder(
builtin=search_pb2.SortOrder.Builtin(
column=order.key, reverse=reverse
)
)
# eg, user is ordering on an add-on field with the add-on not installed
print(f"{order} is not a valid sort order.")
return _pb.SortOrder(none=generic_pb2.Empty())
return search_pb2.SortOrder(none=generic_pb2.Empty())
def find_and_replace(
self,

View file

@ -25,12 +25,12 @@ from typing import Any
from weakref import ref
import anki
from anki import backend_pb2 as _pb
from anki import configs_pb2
from anki.collection import OpChanges
from anki.errors import NotFoundError
from anki.utils import from_json_bytes, to_json_bytes
Config = _pb.Config
Config = configs_pb2.Config
class ConfigManager:

1
pylib/anki/configs_pb2.pyi Symbolic link
View file

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/configs_pb2.pyi

View file

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/deckconfig_pb2.pyi

1
pylib/anki/decks_pb2.pyi Symbolic link
View file

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/decks_pb2.pyi

1
pylib/anki/notes_pb2.pyi Symbolic link
View file

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/notes_pb2.pyi

View file

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/notetypes_pb2.pyi

View file

@ -4,12 +4,15 @@
from __future__ import annotations
import anki
import anki.backend_pb2 as _pb
from anki import decks_pb2
from anki import decks_pb2, scheduler_pb2
from anki.collection import OpChanges, OpChangesWithCount, OpChangesWithId
from anki.config import Config
SchedTimingToday = _pb.SchedTimingTodayResponse
SchedTimingToday = scheduler_pb2.SchedTimingTodayResponse
CongratsInfo = scheduler_pb2.CongratsInfoResponse
UnburyDeck = scheduler_pb2.UnburyDeckRequest
BuryOrSuspend = scheduler_pb2.BuryOrSuspendCardsRequest
FilteredDeckForUpdate = decks_pb2.FilteredDeckForUpdate
from typing import List, Optional, Sequence
@ -20,11 +23,6 @@ from anki.decks import DeckConfigDict, DeckId, DeckTreeNode
from anki.notes import NoteId
from anki.utils import ids2str, intTime
CongratsInfo = _pb.CongratsInfoResponse
UnburyDeck = _pb.UnburyDeckRequest
BuryOrSuspend = _pb.BuryOrSuspendCardsRequest
FilteredDeckForUpdate = decks_pb2.FilteredDeckForUpdate
class SchedulerBase:
"Actions shared between schedulers."

View file

@ -11,8 +11,7 @@ from heapq import *
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
import anki # pylint: disable=unused-import
import anki.backend_pb2 as _pb
from anki import hooks
from anki import hooks, scheduler_pb2
from anki.cards import Card, CardId
from anki.consts import *
from anki.decks import DeckConfigDict, DeckDict, DeckId
@ -20,8 +19,8 @@ from anki.lang import FormatTimeSpan
from anki.scheduler.legacy import SchedulerBaseWithLegacy
from anki.utils import ids2str, intTime
CountsForDeckToday = _pb.CountsForDeckTodayResponse
SchedTimingToday = _pb.SchedTimingTodayResponse
CountsForDeckToday = scheduler_pb2.CountsForDeckTodayResponse
SchedTimingToday = scheduler_pb2.SchedTimingTodayResponse
# legacy type alias
QueueConfig = Dict[str, Any]

View file

@ -14,7 +14,7 @@ from __future__ import annotations
from typing import List, Literal, Sequence, Tuple
import anki.backend_pb2 as _pb
from anki import scheduler_pb2
from anki.cards import Card
from anki.collection import OpChanges
from anki.consts import *
@ -24,10 +24,10 @@ from anki.scheduler.legacy import SchedulerBaseWithLegacy
from anki.types import assert_exhaustive
from anki.utils import intTime
QueuedCards = _pb.QueuedCards
SchedulingState = _pb.SchedulingState
NextStates = _pb.NextCardStates
CardAnswer = _pb.CardAnswer
QueuedCards = scheduler_pb2.QueuedCards
SchedulingState = scheduler_pb2.SchedulingState
NextStates = scheduler_pb2.NextCardStates
CardAnswer = scheduler_pb2.CardAnswer
class Scheduler(SchedulerBaseWithLegacy):
@ -171,7 +171,7 @@ class Scheduler(SchedulerBaseWithLegacy):
##########################################################################
# fixme: move these into tests_schedv2 in the future
def _interval_for_state(self, state: _pb.SchedulingState) -> int:
def _interval_for_state(self, state: scheduler_pb2.SchedulingState) -> int:
kind = state.WhichOneof("value")
if kind == "normal":
return self._interval_for_normal_state(state.normal)
@ -181,7 +181,9 @@ class Scheduler(SchedulerBaseWithLegacy):
assert_exhaustive(kind)
return 0 # unreachable
def _interval_for_normal_state(self, normal: _pb.SchedulingState.Normal) -> int:
def _interval_for_normal_state(
self, normal: scheduler_pb2.SchedulingState.Normal
) -> int:
kind = normal.WhichOneof("value")
if kind == "new":
return 0
@ -196,7 +198,7 @@ class Scheduler(SchedulerBaseWithLegacy):
return 0 # unreachable
def _interval_for_filtered_state(
self, filtered: _pb.SchedulingState.Filtered
self, filtered: scheduler_pb2.SchedulingState.Filtered
) -> int:
kind = filtered.WhichOneof("value")
if kind == "preview":

View file

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/scheduler_pb2.pyi

1
pylib/anki/search_pb2.pyi Symbolic link
View file

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/search_pb2.pyi

View file

@ -1,12 +1,12 @@
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import anki.backend_pb2 as _pb
from anki import sync_pb2
# public exports
SyncAuth = _pb.SyncAuth
SyncOutput = _pb.SyncCollectionResponse
SyncStatus = _pb.SyncStatusResponse
SyncAuth = sync_pb2.SyncAuth
SyncOutput = sync_pb2.SyncCollectionResponse
SyncStatus = sync_pb2.SyncStatusResponse
# Legacy attributes some add-ons may be using

1
pylib/anki/sync_pb2.pyi Symbolic link
View file

@ -0,0 +1 @@
../../bazel-bin/pylib/anki/sync_pb2.pyi

View file

@ -27,7 +27,7 @@ except ImportError as e:
from flask import Response
from anki import Collection
from anki.backend_pb2 import SyncServerMethodRequest
from anki.sync_pb2 import SyncServerMethodRequest
Method = SyncServerMethodRequest.Method # pylint: disable=no-member

View file

@ -4,7 +4,7 @@
use serde_json::Value;
use super::Backend;
pub(super) use crate::backend_proto::config_service::Service as ConfigService;
pub(super) use crate::backend_proto::configs_service::Service as ConfigsService;
use crate::{
backend_proto as pb,
backend_proto::config::{bool::Key as BoolKeyProto, string::Key as StringKeyProto},
@ -52,7 +52,7 @@ impl From<pb::config::String> for StringKey {
}
}
impl ConfigService for Backend {
impl ConfigsService for Backend {
fn get_config_json(&self, input: pb::String) -> Result<pb::Json> {
self.with_col(|col| {
let val: Option<Value> = col.get_config_optional(input.val.as_str());

View file

@ -40,7 +40,7 @@ use self::{
card::CardsService,
cardrendering::CardRenderingService,
collection::CollectionService,
config::ConfigService,
config::ConfigsService,
deckconfig::DeckConfigService,
decks::DecksService,
i18n::I18nService,
@ -48,7 +48,7 @@ use self::{
notes::NotesService,
notetypes::NotetypesService,
progress::ProgressState,
scheduler::SchedulingService,
scheduler::SchedulerService,
search::SearchService,
stats::StatsService,
sync::{SyncService, SyncState},
@ -117,11 +117,11 @@ impl Backend {
pb::ServiceIndex::from_i32(service as i32)
.ok_or_else(|| AnkiError::invalid_input("invalid service"))
.and_then(|service| match service {
pb::ServiceIndex::Scheduling => SchedulingService::run_method(self, method, input),
pb::ServiceIndex::Scheduler => SchedulerService::run_method(self, method, input),
pb::ServiceIndex::Decks => DecksService::run_method(self, method, input),
pb::ServiceIndex::Notes => NotesService::run_method(self, method, input),
pb::ServiceIndex::Notetypes => NotetypesService::run_method(self, method, input),
pb::ServiceIndex::Config => ConfigService::run_method(self, method, input),
pb::ServiceIndex::Configs => ConfigsService::run_method(self, method, input),
pb::ServiceIndex::Sync => SyncService::run_method(self, method, input),
pb::ServiceIndex::Tags => TagsService::run_method(self, method, input),
pb::ServiceIndex::DeckConfig => DeckConfigService::run_method(self, method, input),

View file

@ -5,7 +5,7 @@ mod answering;
mod states;
use super::Backend;
pub(super) use crate::backend_proto::scheduling_service::Service as SchedulingService;
pub(super) use crate::backend_proto::scheduler_service::Service as SchedulerService;
use crate::{
backend_proto::{self as pb},
prelude::*,
@ -16,7 +16,7 @@ use crate::{
stats::studied_today,
};
impl SchedulingService for Backend {
impl SchedulerService for Backend {
/// This behaves like _updateCutoff() in older code - it also unburies at the start of
/// a new day.
fn sched_timing_today(&self, _input: pb::Empty) -> Result<pb::SchedTimingTodayResponse> {

View file

@ -14,11 +14,15 @@ macro_rules! protobuf {
}
protobuf!(backend);
protobuf!(cards);
protobuf!(collection);
protobuf!(configs);
protobuf!(deckconfig);
protobuf!(decks);
protobuf!(generic);
protobuf!(i18n);
protobuf!(notes);
protobuf!(notetypes);
protobuf!(decks);
protobuf!(deckconfig);
protobuf!(i18n);
protobuf!(cards);
protobuf!(generic);
protobuf!(collection);
protobuf!(scheduler);
protobuf!(search);
protobuf!(sync);

View file

@ -3,11 +3,11 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import type { Backend } from "lib/proto";
import type { Scheduler } from "lib/proto";
import { buildNextLearnMsg } from "./lib";
import { bridgeLink } from "lib/bridgecommand";
export let info: Backend.CongratsInfoResponse;
export let info: Scheduler.CongratsInfoResponse;
import * as tr from "lib/i18n";
const congrats = tr.schedulingCongratulationsFinished();

View file

@ -1,19 +1,19 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { Backend } from "lib/proto";
import { Scheduler } from "lib/proto";
import { postRequest } from "lib/postrequest";
import { naturalUnit, unitAmount, unitName } from "lib/time";
import * as tr from "lib/i18n";
export async function getCongratsInfo(): Promise<Backend.CongratsInfoResponse> {
return Backend.CongratsInfoResponse.decode(
export async function getCongratsInfo(): Promise<Scheduler.CongratsInfoResponse> {
return Scheduler.CongratsInfoResponse.decode(
await postRequest("/_anki/congratsInfo", "")
);
}
export function buildNextLearnMsg(info: Backend.CongratsInfoResponse): string {
export function buildNextLearnMsg(info: Scheduler.CongratsInfoResponse): string {
const secsUntil = info.secsUntilNextLearn;
// next learning card not due (/ until tomorrow)?
if (secsUntil == 0 || secsUntil > 86_400) {

View file

@ -6,4 +6,5 @@ import Backend = anki.backend;
import Cards = anki.cards;
import DeckConfig = anki.deckconfig;
import Notetypes = anki.notetypes;
export { Backend, Cards, DeckConfig, Notetypes };
import Scheduler = anki.scheduler;
export { Backend, Cards, DeckConfig, Notetypes, Scheduler };

View file

@ -1,26 +1,26 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { Backend } from "lib/proto";
import { Scheduler } from "lib/proto";
import { postRequest } from "lib/postrequest";
async function getNextStates(): Promise<Backend.NextCardStates> {
return Backend.NextCardStates.decode(
async function getNextStates(): Promise<Scheduler.NextCardStates> {
return Scheduler.NextCardStates.decode(
await postRequest("/_anki/nextCardStates", "")
);
}
async function setNextStates(
key: string,
states: Backend.NextCardStates
states: Scheduler.NextCardStates
): Promise<void> {
const data: Uint8Array = Backend.NextCardStates.encode(states).finish();
const data: Uint8Array = Scheduler.NextCardStates.encode(states).finish();
await postRequest("/_anki/setNextCardStates", data, { key });
}
export async function mutateNextCardStates(
key: string,
mutator: (states: Backend.NextCardStates) => void
mutator: (states: Scheduler.NextCardStates) => void
): Promise<void> {
const states = await getNextStates();
mutator(states);