Ejemplo n.º 1
0
def main(fd, username, password):
    ankipath = "./empty-user/collection.anki2"
    col = anki.Collection(ankipath, log=True)

    k = col.backend.sync_login(username=username, password=password)

    col.backend.full_download(k)

    with fd:
        decks = json.load(fd)['decks']
        decks = list(map(lambda deck: parseDeck(deck), decks))

    model = get_model(col)

    anki_deck = next(
        filter(lambda d: d['name'] == "Roam2Anki", col.decks.all()))
    anki_deck_id = anki_deck['id']

    notes = [anki.notes.Note(col, id=noteid) for noteid in col.find_notes("")]

    for card in decks[0].cards:
        success = update_note(notes, card)

        if not success:
            print("should create this card", card)
            add_note(col, model, anki_deck_id, card)

    col.save(trx=False)

    col.backend.full_upload(k)
Ejemplo n.º 2
0
    def test_make_anki_deck_from_srt_file(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            apkg_path = make_anki_deck_from_srt_file(self.TEST_SRT_PATH,
                                                     name="test",
                                                     output_dir=Path(tmpdir))
            self.assertTrue(apkg_path.is_file())

            with zipfile.ZipFile(apkg_path) as zip:
                zip.extractall(tmpdir)

            anki2_path = Path(tmpdir) / "collection.anki2"
            collection = anki.Collection(str(anki2_path))
            anki_cards = [
                collection.getCard(card_id)
                for card_id in collection.findCards("")
            ]

            self.assertEqual(len(anki_cards), 6)

            anki_cards_as_text = [
                get_anki_card_as_text(card) for card in anki_cards
            ]
            self.assertEqual(
                {f"{card.front}{card.back}"
                 for card in self.EXPECTED_CARDS}, set(anki_cards_as_text))
def main():
    args = parse_arguments()
    validate_args(args)

    if args.url:
        # Download the CSV to a tempfile
        csv_path = download_csv(args.url)
    elif args.path:
        # Use an existing CSV file. We convert this to an absolute path because
        # CWD might change later
        csv_path = os.path.abspath(args.path)
    else:
        assert False  # Should never reach here

    if args.no_anki_connect:
        import anki

        # Normally you use aqt.mw.col to access the collection, but we don't want
        # to use the GUI. Note that this changes the cwd to the Anki
        # collection.media directory
        col = anki.Collection(args.col)

        import_csv(col, csv_path, args.deck, args.note, args.allow_html,
                   args.skip_header)
        print('[W] Cards cannot be automatically synced, '
              'open Anki to sync them manually')
    else:
        send_to_anki_connect(csv_path, args.deck, args.note)
        print('[+] Syncing')
        invoke_ac("sync")

    # If we downloaded this file from a URL, clean it up
    if args.url:
        os.remove(csv_path)
        print('[+] Removed temporary files')
Ejemplo n.º 4
0
def main():
    # opens the collection
    log.info('open collection: %s', options.file)
    cwd = os.getcwd()
    col = anki.Collection(path=options.file)
    work_dir = os.getcwd()
    os.chdir(cwd)

    kd = KanjiDamage(col, log)

    # should update kanji damage deck?
    if options.reset_kd:
        kd.reset(options.kd_file)

    # finds the kanji damage deck and model
    if not kd.get_deck():
        sys.exit('{0}: error: {1}'.format(
            sys.argv[0],
            'couldn\'t find KanjiDamage[ Reordered] deck in the collection, try using option -r to import it'
        ))
    kd_model = kd.get_model()
    if not kd_model:
        sys.exit('{0}: error: {1}'.format(
            sys.argv[0],
            'couldn\'t find KanjiDamage model in the collection, try using option -r to import it'
        ))

    # updates kanji damage deck
    if options.update_kd:
        log.info('updating %s...', kd_model['name'])
        # removes media files
        if options.force_download:
            shutil.rmtree(os.path.join(col.media.dir(), 'assets'),
                          ignore_errors=True)
            shutil.rmtree(os.path.join(col.media.dir(), 'visualaids'),
                          ignore_errors=True)
        kd.update()

    # recreates the kanji damage words deck
    _, kdw_deck = kdw_create(col, kd)

    log.info('writing output file %s...', options.output)
    exporter = AnkiPackageExporter(col)
    exporter.includeSched = False
    exporter.includeMedia = True
    exporter.includeTags = True
    exporter.did = kdw_deck['id']

    out_path = os.path.join(os.getcwd(), options.output)
    os.chdir(work_dir)
    exporter.exportInto(out_path)
    log.info('all is well!')
    col.close()
Ejemplo n.º 5
0
    def _init_load_collection(self, base, path):
        """Load the Anki collection"""
        from pathlib import Path
        from sqlite3 import OperationalError

        import anki
        from aqt.profiles import ProfileManager

        import click

        # Save CWD (because Anki changes it)
        save_cwd = os.getcwd()

        if path is None:
            basepath = Path(base)
            if not (basepath / 'prefs21.db').exists():
                click.echo('Invalid base path!')
                click.echo(f'path = {basepath.absolute()}')
                raise click.Abort()

            # Initialize a profile manager to get an interface to the profile
            # settings and main database path; also required for syncing
            self.pm = ProfileManager(base)
            self.pm.setupMeta()
            self.pm.load(self.pm.profiles()[0])

            # Load the main Anki database/collection
            path = self.pm.collectionPath()
        else:
            self.pm = None

        try:
            self.col = anki.Collection(path)
        except AssertionError:
            click.echo('Path to database is not valid!')
            click.echo(f'path = {path}')
            raise click.Abort()
        except OperationalError:
            click.echo('Database is NA/locked!')
            raise click.Abort()

        # Restore CWD (because Anki changes it)
        os.chdir(save_cwd)
Ejemplo n.º 6
0
    def _init_load_collection(self, base, path, profile):
        """Load the Anki collection"""
        # Save CWD (because Anki changes it)
        save_cwd = os.getcwd()

        if path is None:
            if base is None:
                click.echo('Base path is not properly set!')
                raise click.Abort()

            basepath = Path(base)
            if not (basepath / 'prefs21.db').exists():
                click.echo('Invalid base path!')
                click.echo(f'path = {basepath.absolute()}')
                raise click.Abort()

            # Initialize a profile manager to get an interface to the profile
            # settings and main database path; also required for syncing
            self.pm = ProfileManager(base)
            self.pm.setupMeta()

            if profile is None:
                profile = self.pm.profiles()[0]

            # Load the main Anki database/collection
            self.pm.load(profile)
            path = self.pm.collectionPath()
        else:
            self.pm = None

        try:
            self.col = anki.Collection(path)
        except AssertionError:
            click.echo('Path to database is not valid!')
            click.echo(f'path = {path}')
            raise click.Abort()
        except anki.errors.DBError:
            click.echo('Database is NA/locked!')
            raise click.Abort()

        # Restore CWD (because Anki changes it)
        os.chdir(save_cwd)
Ejemplo n.º 7
0
def main():
    parser = argparser()
    args = parser.parse_args()

    csv_filename = args.csv_filename
    deck_name = args.deck_name
    apkg_filename = args.apkg_filename
    media_directory = args.media_directory
    model_name = args.model_name

    # this is removed at the end of the program
    TMPDIR = tempfile.mkdtemp()

    collection = anki.Collection(os.path.join(TMPDIR, 'collection.anki2'))

    deck_id = collection.decks.id(deck_name)
    deck = collection.decks.get(deck_id)

    if model_name == 'Image Occlusion Enhanced':
        model = add_io_model(collection)
    else:
        model = collection.models.byName(model_name).copy()

    model['did'] = deck_id

    collection.models.update(model)
    collection.models.setCurrent(model)
    collection.models.save(model)

    importer = TextImporter(collection, csv_filename)
    importer.allowHTML = True
    importer.initMapping()
    importer.run()

    for media_file in os.listdir(media_directory):
        os.symlink(os.path.join(media_directory, media_file),
                   os.path.join(TMPDIR, 'collection.media', media_file))

    export = AnkiPackageExporter(collection)
    export.exportInto(apkg_filename)

    shutil.rmtree(TMPDIR)
Ejemplo n.º 8
0
cmd_folder = "/usr/share/anki"
if cmd_folder not in sys.path:
    sys.path.insert(0, cmd_folder)

try:
    import anki
    import anki.sync
except ImportError:
    sys.exit(
        'anki must be installed to /usr/share/anki. first cd /usr/share/. Then '
        + 'git clone https://github.com/dae/anki')

# the collection should be in the current directory
collection_filename = os.path.dirname(
    os.path.realpath(__file__)) + '/' + config['collection_filename']
collection = anki.Collection(collection_filename, log=True)


def tts(text):
    global ankitts_file
    print "saying: " + text
    write_to_log("saying: " + text)
    # subprocess.call(['flite', '-t', text])
    # subprocess.call(['anki-tts.sh', text])
    subprocess.call([ankitts_file, text])
    # print "done tts"


def crap_tts(text):
    print "saying this crappily: " + text
    write_to_log("saying this crappily: " + text)
Ejemplo n.º 9
0
def create_apkg(meetup_event_url, meetup_api_key, yes, verbose, debug, noop):
    parse_result = urllib.parse.urlparse(meetup_event_url)
    urlname, _, event_id = parse_result.path.split('/')[1:4]
    client = meetup.api.Client(meetup_api_key)

    event = client.GetEvent(id=event_id, urlname=urlname)

    date = datetime.fromtimestamp(event.time / 1000).strftime('%Y-%m-%d')
    filename = 'meetup-rsvps-{}-{}.apkg'.format(urlname, date)

    ### Output confirmation to user

    if verbose or not yes:
        confirmation_details = """\
            We are using the following configuration:
              * Meetup Group: {group}
              * Event Name:   {name}
                    * Date:   {date}
                    * RSVPs:  {rsvps}"""
        confirmation_details = confirmation_details.format(
            group=urlname,
            name=event.name,
            date=date,
            rsvps=event.yes_rsvp_count)
        click.echo(textwrap.dedent(confirmation_details), err=True)

    if not yes:
        click.confirm('Do you want to continue?', abort=True)

    create_path('outputs')

    with tempfile.TemporaryDirectory() as tmpdir:
        collection = anki.Collection(os.path.join(tmpdir, 'collection.anki2'))

        deck_id = collection.decks.id('Meetup: {}'.format(event.name))
        deck = collection.decks.get(deck_id)

        model = collection.models.new("meetup_model")
        model['did'] = deck_id
        model['css'] = ''
        collection.models.addField(model,
                                   collection.models.newField('person_photo'))
        collection.models.addField(model,
                                   collection.models.newField('person_name'))

        tmpl = collection.models.newTemplate('meetup attendee')
        tmpl['qfmt'] = '{{person_photo}}'
        tmpl['afmt'] = '{{FrontSide}}\n\n<hr>\n\n{{person_name}}'
        collection.models.addTemplate(model, tmpl)

        model['id'] = 123456789
        collection.models.update(model)
        collection.models.setCurrent(model)
        collection.models.save(model)

        note = anki.notes.Note(collection, model)
        note['person_photo'] = ''
        note['person_name'] = ''
        note.guid = 0
        collection.addNote(note)

        response = client.GetRsvps(event_id=event_id)
        for rsvp in response.results:
            if not rsvp.get('member_photo'):
                if verbose:
                    click.echo('Skipping {} (no photo)'.format(
                        rsvp['member']['name']),
                               err=True)
                continue
            if verbose:
                click.echo('Fetching ' + rsvp['member']['name'], err=True)
            name = rsvp['member']['name']
            url = rsvp['member_photo']['photo_link']
            path, contents = retrieveURL(url)
            local_file = collection.media.writeData(path, contents)

            note = collection.newNote()
            note['person_photo'] = '<img src="{}" />'.format(local_file)
            note['person_name'] = name
            note.guid = rsvp['rsvp_id']
            collection.addNote(note)

        # Update media database, just in case.
        # Not sure if this is necessary
        collection.media.findChanges()

        output_filepath = collection.media._oldcwd + '/outputs/' + filename

        export = AnkiPackageExporter(collection)
        export.exportInto(output_filepath)
        click.echo('output_filepath: ' + output_filepath)
Ejemplo n.º 10
0
#!/usr/bin/env python3

# Example how to make the guids already imported cards compatible with genanki

import sys

sys.path.insert(0, "/usr/share/anki/")
import anki

sys.path.insert(0, "../genanki/")
import genanki.util as util


deck_name = "General::IT"
ac = anki.Collection("/home/tr/.local/share/Anki2/test/collection.anki2")


deck_id = ac.decks.id(deck_name)
ac.decks.select(deck_id)

notes = ac.findNotes("deck:" + deck_name)

changed = 0
for nid in notes:
    note = ac.getNote(nid)

    new_guid = util.guid_for(note["example"])
    if note.guid != new_guid:
        note.guid = new_guid
        note.flush(mod=True)
        print("updated " + note["example"] + ": " + str(note.guid))
Ejemplo n.º 11
0
 def setup(self):
     # TODO make this less messy
     colf = tempfile.NamedTemporaryFile(suffix='.anki2')
     colf_name = colf.name
     colf.close()  # colf is deleted
     self.col = anki.Collection(colf_name)
Ejemplo n.º 12
0
DOCS_PATH = '/home/cowley/Documents/'
TMP_PATH = '/tmp/'
APKG_PATH = DOCS_PATH + 'accelerated_spanish_empty.apkg'
CSV_PATH = DOCS_PATH + 'spanish5.csv'
TMP_COL_PATH = TMP_PATH
DECK_NAME = 'accelerated spanish'
DECK_ID = None  #1518389138178
MODEL_NAME = 'Basic'
QUESTION_FIELD = 'Front'
ANSWER_FIELD = 'Back'
MEDIA_PATH = '/home/cowley/.local/share/Anki2/User 1/collection.media/'

with open(CSV_PATH) as f:
    reader = csv.reader(f, delimiter='\t')

    collection = anki.Collection(TMP_COL_PATH +
                                 str(random.randint(10**9, 10**10)) + '.anki2')
    api = AnkiPackageImporter(collection, APKG_PATH)
    api.run()

    collection = api.col

    # get deck
    try:
        deck_id = collection.decks.id(DECK_NAME)
    except:
        print('invalid deck name')
        raise
    collection.decks.select(deck_id)

    # get model to use
    try:
Ejemplo n.º 13
0
                            col: anki.collection._Collection):
    change_model_to_cloze(col)
    note = col.newNote(forDeck=False)
    note.model()['did'] = col.decks.id("200")  # type: ignore
    left_sap, right_sap = [sap.remove_subs() for sap in dap.split()]
    if left_sap.get_size()[1] > 400:
        left_sap, right_sap = (left_sap.resize(scale=0.5),
                               right_sap.resize(scale=0.5))
    left_pic = col.media.addFile(left_sap.get_filename())
    right_pic = col.media.addFile(right_sap.get_filename())
    note.fields[0] = get_cloze_field(left_pic, dap.left_adjective, right_pic,
                                     dap.right_adjective)
    note.tags = (list(SHARED_TAGS) +
                 [dap.left_adjective, dap.right_adjective] + ['double'])
    print("Adding an image for " +
          str((dap.left_adjective, dap.right_adjective)))
    col.addNote(note)
    note.flush()


def main(anki_collection: anki.collection._Collection):
    daps = load_images()
    for dap in daps:
        add_a_double_cloze_note(dap, col)
    col.save()


if __name__ == "__main__":
    with closing(anki.Collection(str(ANKI_DB))) as col:
        main(col)
Ejemplo n.º 14
0
import sys
import tempfile

import anki
from anki.exporting import AnkiPackageExporter
from anki.importing.csvfile import TextImporter

if len(sys.argv) < 3:
    sys.exit('Usage: {0} INPUT.csv OUTPUT.apkg'.format(sys.argv[0]))

csv_file = os.path.abspath(sys.argv[1])
apkg_file = os.path.abspath(sys.argv[2])

temp_dir = tempfile.gettempdir()

collection = anki.Collection(os.path.join(temp_dir, 'collection.anki2'))

# Create a new deck in the collection
# TODO: it would be nice to use a more user-friendly deck name (e.g. from a README?)
deck_id = collection.decks.id(os.path.splitext(os.path.basename(apkg_file))[0])

# Set note type to Basic (and reversed card)
model = collection.models.byName('Basic (and reversed card)')

# This is necessary so that the import saves new cards into the new deck we just created
model['did'] = deck_id

# Save above changes to the model
collection.models.save(model)

# Add a description to the deck
Ejemplo n.º 15
0
#!/usr/bin/env python3
"""Transform cards from "Fluent Forever" to my format."""
# Download https://github.com/dae/anki and extract anki/anki
import anki
col = anki.Collection('grzesiek/collection.anki2')
minimal_pair_model = col.models.byName('MinimalPair')
gmp_notes = col.findNotes('"deck:Foreign Languages::*German Minimal Pairs*"')
german_deck = col.decks.byName('Foreign Languages::German')
processed = set()
for gmp_note in [col.getNote(n_id) for n_id in gmp_notes]:
    gmp_note_fields = dict(gmp_note.items())
    new_note = anki.notes.Note(col, minimal_pair_model)
    new_note.model()['did'] = german_deck['id']
    new_note.fields[0] = gmp_note_fields['Recording 1']
    new_note.fields[1] = '{0}<br/>/{1}/'.format(
        gmp_note_fields['Word 1'],
        gmp_note_fields['Word 1 IPA'],
    )
    new_note.fields[2] = gmp_note_fields['Recording 2']
    new_note.fields[3] = '{0}<br/>/{1}/'.format(
        gmp_note_fields['Word 2'],
        gmp_note_fields['Word 2 IPA'],
    )
    new_note_key = (new_note.fields[0], new_note.fields[1])
    if new_note_key not in processed:
        col.addNote(new_note)
        processed.add(new_note_key)

picture_words_model = col.models.byName('Final 2. Picture Words')
basic_fb_model = col.models.byName('BasicFB')
pwm_notes = [
Ejemplo n.º 16
0
#         addon.write(entry.path, entry.name)
#     addon.close()

if __name__ == "__main__":
    print("Rebuilding...")
    if not config.BUILDDIR.exists():
        config.BUILDDIR.mkdir()

    if not config.DISTDIR.exists():
        config.DECKFILE.mkdir()

    # creating new collection file
    print(f"Saving to {config.DECKFILE}...")

    config.DECKFILE.unlink(missing_ok=True)
    coll = anki.Collection(config.DECKFILE, server=False, log=False)
    coll.decks.add_config("Interactive Demo")

    create_models(coll)
    print(f"Added {len(coll.models.all())} models")

    create_notes(coll)
    print(f"Added {coll.noteCount()} notes")

    # remove all default models
    for m in coll.models.all():
        if m['name'] not in models_def.MODELS:
            coll.models.remove(m['id'])

    coll.close()
Ejemplo n.º 17
0
import anki
from anki.exporting import AnkiPackageExporter
import os

TMPDIR = "/tmp"
FBASENAME = "test"
FONAME = "/home/rainman/testdata/test.apkg"

collection = anki.Collection(os.path.join(TMPDIR, 'collection.anki2'))

deck_id = collection.decks.id(FBASENAME + "_deck")
deck = collection.decks.get(deck_id)

deck['mid'] = collection.models.byName('Cloze')

model = collection.models.new(FBASENAME + "_model")
model['tags'].append(FBASENAME + "_tag")
model['did'] = deck_id
model['css'] = """
.card {
  font-family: arial;
  font-size: 20px;
  text-align: center;
  color: black;
  background-color: white;
}
.from {
  font-style: italic;
}
"""