lervag / apy Goto Github PK
View Code? Open in Web Editor NEWCLI script for interacting with local Anki collection
License: MIT License
CLI script for interacting with local Anki collection
License: MIT License
It would be great if there was support for multiple decks.
For example, the syntax for creating a card with apy add
in a deck called "Programming" could be:
deck: Programming
model: Basic
tags: marked
# Note
## Front
## Back
Currently it seems that cards are put into the deck that was last used.
Hi!
Wiki page found here https://github.com/lervag/apy/wiki/Vim mentions personalized snippets pointing to https://github.com/lervag/dotvim/blob/master/personal/ftplugin/markdown.vim#L12-L17 which leads to 404.
Could you please update it?
Thanks!
Not sure if it's a bug or feature request.
I just created a new card (seems to work well, that's awesome!)
And I noticed that all <b>
tags got converted and the inner word made bold (like <b>curriculum</b>
.
But <u>
tags are not converted.
Is the boldening process made on your side in the editor, or should I be looking elsewhere (zsh, alacritty) for that ?
Thanks for the support!
$ apy add
Traceback (most recent call last):
File "/home/ali/.local/bin/apy", line 11, in <module>
load_entry_point('apy==0.1', 'console_scripts', 'apy')()
File "/home/ali/.local/lib/python3.8/site-packages/click/core.py", line 764, in __call__
return self.main(*args, **kwargs)
File "/home/ali/.local/lib/python3.8/site-packages/click/core.py", line 717, in main
rv = self.invoke(ctx)
File "/home/ali/.local/lib/python3.8/site-packages/click/core.py", line 1137, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "/home/ali/.local/lib/python3.8/site-packages/click/core.py", line 956, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/home/ali/.local/lib/python3.8/site-packages/click/core.py", line 555, in invoke
return callback(*args, **kwargs)
File "/home/ali/.local/lib/python3.8/site-packages/apy/cli.py", line 62, in add
with Anki(cfg['base']) as a:
File "/home/ali/.local/lib/python3.8/site-packages/apy/anki.py", line 15, in __init__
self._init_load_collection(base, path)
File "/home/ali/.local/lib/python3.8/site-packages/apy/anki.py", line 41, in _init_load_collection
basepath = Path(base)
File "/usr/lib/python3.8/pathlib.py", line 1018, in __new__
self = cls._from_parts(args, init=False)
File "/usr/lib/python3.8/pathlib.py", line 667, in _from_parts
drv, root, parts = self._parse_args(args)
File "/usr/lib/python3.8/pathlib.py", line 651, in _parse_args
a = os.fspath(a)
TypeError: expected str, bytes or os.PathLike object, not NoneType
Anki is at /usr/share/anki
Cloned and pip-installed as in README (not developer version)
It might be more flexible to rely on external formatting tools to handle coloring the output, and will allow users to customize their colors.
I usually use grc to colorize shell output.
And tools like pygmentize are popular to colorize code.
Eg for this grc conf:
# Colorize apy list output
regexp=\w+
colours="\033[38;5;20m"
count=more
=====
regexp=^(\w+:)
colours=red
count=more
=====
regexp=(cid|ease|lapses|due|model): ([\w.%]+)
colours=yellow, blue
count=more
=====
It's a low priority, but it'd be nice if the output of apy list
was more readable, especially when using markdown: false
. I'm not sure what would be a good way to achieve that.
I finally found some time to try creating cards with apy
.
When I try to type apy add
into the terminal, my editor opens with:
model: Basic
tags: marked
# Note
## Front
## Back
**CATEGORY**
## Hint
I change it to:
model: Basic
tags: marked
# Note
## Front
What is this?
## Back
This is a test!
## Hint
test
When I exit my editor, the following error occurs:
Database was modified.
Remember to sync!
Traceback (most recent call last):
File "/home/skervim/anaconda3/envs/rl/bin/apy", line 11, in <module>
load_entry_point('apy', 'console_scripts', 'apy')()
File "/home/skervim/anaconda3/envs/rl/lib/python3.7/site-packages/click/core.py", line 764, in __call__
return self.main(*args, **kwargs)
File "/home/skervim/anaconda3/envs/rl/lib/python3.7/site-packages/click/core.py", line 717, in main
rv = self.invoke(ctx)
File "/home/skervim/anaconda3/envs/rl/lib/python3.7/site-packages/click/core.py", line 1137, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "/home/skervim/anaconda3/envs/rl/lib/python3.7/site-packages/click/core.py", line 956, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/home/skervim/anaconda3/envs/rl/lib/python3.7/site-packages/click/core.py", line 555, in invoke
return callback(*args, **kwargs)
File "/home/skervim/apy/apy/cli.py", line 58, in add
notes = a.add_notes_with_editor(tags, model)
File "/home/skervim/apy/apy/anki.py", line 291, in add_notes_with_editor
return self.add_notes_from_file(tf.name)
File "/home/skervim/apy/apy/anki.py", line 297, in add_notes_from_file
tags)
File "/home/skervim/apy/apy/anki.py", line 324, in add_notes_from_list
note['markdown']))
File "/home/skervim/apy/apy/anki.py", line 354, in _add_note
return Note(self, note)
File "/home/skervim/apy/apy/note.py", line 11, in __init__
self.fields = [x for x, y in self.n.items()]
File "/home/skervim/anki/anki/notes.py", line 89, in items
for ord, f in sorted(self._fmap.values())]
File "/home/skervim/anki/anki/notes.py", line 89, in <listcomp>
for ord, f in sorted(self._fmap.values())]
IndexError: list index out of range
Any idea what I am doing wrong?
Python 3.7.4
Ubuntu 18.04
> apy --help
Traceback (most recent call last):
File "/home/me/miniconda3/bin/apy", line 11, in <module>
load_entry_point('apy', 'console_scripts', 'apy')()
File "/home/me/miniconda3/lib/python3.7/site-packages/pkg_resources/__init__.py", line 489, in load_entry_point
return get_distribution(dist).load_entry_point(group, name)
File "/home/me/miniconda3/lib/python3.7/site-packages/pkg_resources/__init__.py", line 2852, in load_entry_point
return ep.load()
File "/home/me/miniconda3/lib/python3.7/site-packages/pkg_resources/__init__.py", line 2443, in load
return self.resolve()
File "/home/me/miniconda3/lib/python3.7/site-packages/pkg_resources/__init__.py", line 2449, in resolve
module = __import__(self.module_name, fromlist=['__name__'], level=0)
File "/home/me/code/apy/apy/cli.py", line 6, in <module>
from apy.anki import Anki
File "/home/me/code/apy/apy/anki.py", line 10, in <module>
from aqt.profiles import ProfileManager
File "/usr/share/anki/aqt/__init__.py", line 14, in <module>
from aqt.qt import *
File "/usr/share/anki/aqt/qt.py", line 6, in <module>
import sip
ModuleNotFoundError: No module named 'sip'
But I installed sip, strange, any hints, thanks in advance.
A very useful anki addon for the creation of cards with anki desktop is the frozen field addon.
From its description: "Anki supports sticky fields. A sticky field is a field whose value is not deleted when you switch to a different note. This can be very useful if you are making many notes in which a field either has the same value or changes very little."
It would be awesome if there was a syntax to specify sticky fields for a markdown file from which we create anki cards via apy add-from-file input.md
.
Just like it is possible to specify the note type (model) for several notes in advance:
//input.md
model: Basic
tags: marked
# Note 1
## Front
Question?
## Back
Answer.
# Note 2
tag: silly-tag
## Front
Question?
## Back
Answer
it would be very useful to determine the value of certain fields for all notes in advance.
Hi.
I'm having issues with Anki + Qt5 + Mozc + ibus + Wayland and from today cannot use Japanese within Anki anymore (or I'd have to write text in another window and constantly copy-paste over to the Anki browser).
I found apy as an alternative that would allow me to edit my cards/notes outside of Anki. Thanks for making it!
It's a bit unclear how I should be using apy thought.
Here's the flow I was expecting:
apy list -v <pattern>
should give me an id next to each match, that I can use in further queriesapy edit <id>
should open my editor (vim) and let me edit the card/noteBut there doesn't seem to be a edit command yet ? π
Is it not possible to edit cards with apy ? π°
Right now, when searching for cards, I see:
$ apy list -v εη°Ώ
reverting to stock json
Q: <div id="kard"> <div class="tags">KanjiInContext meaning: εη°Ώγδ½γζγζ₯ζ¬γ§γ―γγγγγι γθ±θͺγ§γ―γ’γ«γγ‘γγγι γ«γγγ <script type="text/x-mathjax-config"> MathJax.Hub.processSectionDelay = 0; MathJax.Hub.Config({ me
A: <div id="kard"> <div class="tags" id='tags'>KanjiInContext meaning: εη°Ώγδ½γζγζ₯ζ¬γ§γ―γγγγγι γθ±θͺγ§γ―γ’γ«γγ‘γγγι γ«γγγ <div id='extra'>εη°Ώγ»γγγΌγ»register / list of names ι γ»γγ
γγ»order
ease: 250.0% lapses: 0 model: Basic
Q: <div id="kard"> <div class="tags">KanjiInContext reading: <u>εη°Ώ</u>γδ½ζγγ <script type="text/x-mathjax-config"> MathJax.Hub.processSectionDelay = 0; MathJax.Hub.Config({ messageStyle:"none", showProce
A: <div id="kard"> <div class="tags" id='tags'>KanjiInContext reading: <u>εη°Ώ</u>γδ½ζγγ <div id='extra'>γγγΌγ»name roster, list of members <script type="text/x-mathjax-config"> MathJax.Hub
ease: 250.0% lapses: 0 model: Basic
Q: <div id="kard"> <div class="tags"> <div id="cloze-content">εεγ<span class=cloze>[reading εη°Ώ]</span>γ»<u>ζΈη±</u>γγ<u>ζΉζΆ</u>γγ <script> const tags = document.getElementsByClassName("tags")[0].innerHTML.split(" ");
A: <div id="kard"> <div class="tags" id='tags'> εεγ<span class=cloze><u>εη°Ώ</u></span>γ»<u>ζΈη±</u>γγ<u>ζΉζΆ</u>γγ <div id='extra'>εη°Ώγ»γγγΌγ»register/list of names ζΈη±γ»γ
γγγ»census, family r
ease: 250.0% lapses: 0 model: Cloze
Q: <div id="kard"> <div class="tags"> <div id="cloze-content">εεγ<u>εη°Ώ</u>γ»<span class=cloze>[reading ζΈη±]</span>γγ<u>ζΉζΆ</u>γγ <script> const tags = document.getElementsByClassName("tags")[0].innerHTML.split(" ");
A: <div id="kard"> <div class="tags" id='tags'> εεγ<u>εη°Ώ</u>γ»<span class=cloze><u>ζΈη±</u></span>γγ<u>ζΉζΆ</u>γγ <div id='extra'>εη°Ώγ»γγγΌγ»register/list of names ζΈη±γ»γ
γγγ»census, family r
ease: 0.0% lapses: 0 model: Cloze
Q: <div id="kard"> <div class="tags"> <div id="cloze-content">εεγ<u>εη°Ώ</u>γ»<u>ζΈη±</u>γγ<span class=cloze>[reading ζΉζΆ]</span>γγ <script> const tags = document.getElementsByClassName("tags")[0].innerHTML.split(" ");
A: <div id="kard"> <div class="tags" id='tags'> εεγ<u>εη°Ώ</u>γ»<u>ζΈη±</u>γγ<span class=cloze><u>ζΉζΆ</u></span>γγ <div id='extra'>εη°Ώγ»γγγΌγ»register/list of names ζΈη±γ»γ
γγγ»census, family r
ease: 230.0% lapses: 1 model: Cloze
It's honestly hard to find out what is the card I'm looking for.
After some time reading the output, I understand that the top 2 questions are 2 Basic questions, and the bottom 3 are 1 Cloze question.
The cloze is the one I was looking forward to update.
Operations that I regularly do are:
randomize
tag to shuffle words in the question with a javascript snippet). apy has that covered, but using matching notes returns several results and I only want to target a single note. I cannot seem able to use html tags in the query ? Allowing ids would be very handy.
$ apy list -v 'εγ<u>εη°Ώ</u>'
reverting to stock json
$ # nothing has been returned!
Really hoping that either all of this is possible and I just missed something, or that you have a roadmap to include these features β€οΈ
I've recently tried installing apy through termux via pipx
. Unfortunately as it cannot detect any version of anki, it cannot be installed.
I had planned on using apy with AnkiDroid to be able to create flashcards from my phone as well. I already knew the base path of the Anki collections in AnkiDroid. Would this be possible?
Thanks for creating apy btw, it's been great at cutting out unnecessary middleware in my PC.
These are my logs
~/dotfiles $ pipx install git+https://github.com/lervag/apy
Fatal error from pip prevented installation. Full pip output in file:
/data/data/com.termux/files/home/.local/pipx/logs/cmd_2023-06-21_15.37.13_pip_errors.log
pip seemed to fail to build package:
git+https://github.com/lervag/apy
Some possibly relevant errors from pip install:
ERROR: Could not find a version that satisfies the requirement anki<3.0.0,>=2.1.63 (from apy) (from versions: 2.1.24+359b9f5c, 2.1.25, 2.1.26, 2.1.28, 2.1.29, 2.1.30, 2.1.31, 2.1.32, 2.1.33, 2.1.34, 2.1.35)
ERROR: No matching distribution found for anki<3.0.0,>=2.1.63
Error installing apy from spec
'git+https://github.com/lervag/apy'.
While answering lervag/dotvim#2 (comment) about wiki.vim, I got an idea to resolve my pain point described in #32 and let me use markdown-only notes editable from my phone.
Would it be possible to diff the data-original-markdown content with the html content and sync the markdown accordingly?
Here's an example:
What is the **caiptal of France**?
<div data-original-markdown="AB...YZ">
What is the <b>caiptal of France</b>?
</div>
<div data-original-markdown="AB...YZ">
What is the <b>capital of France</b>?
</div>
<div data-original-markdown="AB...YZ">
- What is the <b>caiptal of France</b>?
+ What is the <b>capital of France</b>?
</div>
cmp
, and do a find-and-replace operation ?)
What is the **capital of France**?
<div data-original-markdown="CD...WX">
What is the <b>capital of France</b>?
</div>
When I say "Syncing back to apy", I'm just not sure when apy re-extract the markdown from data-original-markdown. If it's on a apy list
& co, then doing this operation on a apy review
+ e
could be the least expensive operation.
I have cards with markup, but when editing the card, the markup disappears.
Demonstration: https://youtu.be/BS4qzy93eS8
Bug introduced in v0.7.2
Reverting to v0.7.1 properly shows the markup:
From #25 (comment), it appears that apy review
without query edits all marked and flagged notes.
Please consider also adding leeched cards in the default apy review
:) (tag: leech
)
That brings another question. How can we query a tag with apy list
? tried apy list 'tag: leech'
without success, and apy list leech
doesn't search in tags.
We could get to complicated schema modifications later.
But, for now, I just wanted to rename a few models.
β apy add
reverting to stock json
Traceback (most recent call last):
File "/home/dori/.local/bin/apy", line 8, in <module>
sys.exit(main())
File "/usr/lib/python3.8/site-packages/click/core.py", line 829, in __call__
return self.main(*args, **kwargs)
File "/usr/lib/python3.8/site-packages/click/core.py", line 782, in main
rv = self.invoke(ctx)
File "/usr/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "/usr/lib/python3.8/site-packages/click/core.py", line 1066, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/usr/lib/python3.8/site-packages/click/core.py", line 610, in invoke
return callback(*args, **kwargs)
File "/home/dori/.local/lib/python3.8/site-packages/apy/cli.py", line 116, in add
notes = a.add_notes_with_editor(tags, model_name, deck)
File "/home/dori/.local/lib/python3.8/site-packages/apy/anki.py", line 381, in add_notes_with_editor
return self.add_notes_from_file(tf.name)
File "/home/dori/.local/lib/python3.8/site-packages/apy/anki.py", line 385, in add_notes_from_file
return self.add_notes_from_list(markdown_file_to_notes(filename),
File "/home/dori/.local/lib/python3.8/site-packages/apy/convert.py", line 53, in markdown_file_to_notes
defaults, notes = _parse_file(filename)
File "/home/dori/.local/lib/python3.8/site-packages/apy/convert.py", line 154, in _parse_file
note['fields'][field] = ''
KeyError: 'fields'
It doesn't appear possible to move existing cards to other decks. Can we have this feature supported?
How can we modify the edit_note_.*.md
template that is used in apy review
?
I'd like to add some vim modelines to improve my experience, like
<!-- vim: setlocal spell spelllang=cjk -->
To disable english spell-check since I'm almost always editing Japanese documents.
syntax like
exc_type: type[BaseException] | None,
where you use |
to mean Union
only works on Python 3.10 and higher. this should either be fixed to use Union
(or in this case, Optional
), or we should bump the required Python version to 3.10.
Given that a pattern can have several matches, it would be nice to see the id of the note in the apy list
output, and use that id with apy review
to edit a single note.
Quick look in the db shows that the associate column is called cid
sqlite> pragma table_info(notes);
cid name type notnull dflt_value pk
--- ----- ------- ------- ---------- --
0 id integer 0 1
Usage:
$ apy list -v εθͺ
Q: question 1 εθͺ
A: answer 1
ease: 250.0% lapses: 0 model: Basic id=1
$ API list -v εθͺ
Q: question 2 εθͺ
A: answer 2
ease: 230.0% lapses: 0 model: Basic id=2
...
$ apy review -i 2
I'll admit that the feature is a low-priority, because it's quite easy to press c
several time in the apy terminal editor until I reach the proper note, then x
to abort.
This is a follow-up to #27 (comment) in #27.
At the moment, unless there has been a lapse or the card's ease has been changed, there is no way to distinguish new cards from learnt ones.
If the card was already learnt, apy reposition will fail
:
$ apy reposition 0 tag:fixme
Can only reposition new cards!
Aborted!
But if I have dozen of cards in the query I'm trying to reposition,] it's very difficult to figure out which one is not new.
Could we see the current type of each cards in apy list -v
?
By card type, I mean seeing "unseen", "young", "mature" as shown in the stats.
I installed apy
with
pip install -e .
The command apy --help
works fine, but when I try to do e.g. apy add
, I get:
Traceback (most recent call last):
File "/home/skervim/anaconda3/envs/rl/bin/apy", line 11, in <module>
load_entry_point('apy', 'console_scripts', 'apy')()
File "/home/skervim/anaconda3/envs/rl/lib/python3.7/site-packages/click/core.py", line 764, in __call__
return self.main(*args, **kwargs)
File "/home/skervim/anaconda3/envs/rl/lib/python3.7/site-packages/click/core.py", line 717, in main
rv = self.invoke(ctx)
File "/home/skervim/anaconda3/envs/rl/lib/python3.7/site-packages/click/core.py", line 1137, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "/home/skervim/anaconda3/envs/rl/lib/python3.7/site-packages/click/core.py", line 956, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/home/skervim/anaconda3/envs/rl/lib/python3.7/site-packages/click/core.py", line 555, in invoke
return callback(*args, **kwargs)
File "/home/skervim/apy/apy/cli.py", line 57, in add
with Anki(BASE) as a:
File "/home/skervim/apy/apy/anki.py", line 14, in __init__
import anki
ModuleNotFoundError: No module named 'anki'
I tried to modify a Basic card and convert it as a Cloze card, but the operation is ignored and the card stays a Basic card.
# Note
nid: 1479342529723
- model: Basic
+ model: Cloze
deck: Kanji in Context
tags: KanjiInContext
markdown: false
Would it be possible to support that operation? Or to add another option in the review editor n: Change Note type
?
From experience, Anki will ask for a full re-upload of the db in that case, which is slow and problematic if for example I already started my reviews from my phone (the sync would lose that data).
Instead, it would be preferable to create a new note with the desired model, then delete the original one (no syncing issue).
If you run apy add
but change your mind and don't add anything (e.g. straight away pressing :wq
in Vim or Ctrl-X
in Nano), it crashes:
[ckp95@ckp95-laptop anki]$ apy add
reverting to stock json
Dupe detected, note was not added!
Question:
Traceback (most recent call last):
File "/home/ckp95/.local/bin/apy", line 8, in <module>
sys.exit(main())
File "/home/ckp95/.local/lib/python3.8/site-packages/click/core.py", line 829, in __call__
return self.main(*args, **kwargs)
File "/home/ckp95/.local/lib/python3.8/site-packages/click/core.py", line 782, in main
rv = self.invoke(ctx)
File "/home/ckp95/.local/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "/home/ckp95/.local/lib/python3.8/site-packages/click/core.py", line 1066, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/home/ckp95/.local/lib/python3.8/site-packages/click/core.py", line 610, in invoke
return callback(*args, **kwargs)
File "/home/ckp95/.local/lib/python3.8/site-packages/apy/cli.py", line 75, in add
click.echo(f'Added note to deck: {decks[0]}')
IndexError: list index out of range
If you run apy add
and add some notes interactively, the directory is left with a .md file containing those notes. I think it would be better that they be deleted afterwards by default, or else saved in the /tmp directory.
[ckp95@ckp95-laptop anki]$ ls
[ckp95@ckp95-laptop anki]$ apy add
reverting to stock json
Added note to deck: All
Review added notes? [y/N]: n
Database was modified.
Remember to sync!
[ckp95@ckp95-laptop anki]$ ls
note_4akr_cca.md
After #59 was merged the GitHub actions step fails due to missing dependency of mypy
:
I believe this is because of this part:
Lines 21 to 29 in 2aae673
But I'm not sure exactly what's wrong. I hope @ckp95 or @denismaciel would be able to help me here?
I am trying to run apy
on my laptop with Linux Mint 21:
Kernel: 5.15.0-56-generic x86_64
Desktop: Cinnamon 5.4.12
Distro: Linux Mint 21 Vanessa (base: Ubuntu 22.04 jammy )
When I run apy info
I get the following error:
Traceback (most recent call last):
File "/home/pietro/.local/bin/apy", line 33, in <module>
sys.exit(load_entry_point('apy', 'console_scripts', 'apy')())
File "/home/pietro/.local/bin/apy", line 25, in importlib_load_entry_point
return next(matches).load()
File "/usr/lib/python3.10/importlib/metadata/__init__.py", line 171, in load
module = import_module(match.group('module'))
File "/usr/lib/python3.10/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 883, in exec_module
File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
File "/home/pietro/git/apy/apy/cli.py", line 8, in <module>
from apy.anki import Anki
File "/home/pietro/git/apy/apy/anki.py", line 7, in <module>
import anki
ModuleNotFoundError: No module named 'anki'
This is what I have done before trying running apy
:
I installed Anki from the official web site https://apps.ankiweb.net/ (anki-2.1.55-linux-qt6). Anki works well.
I cloned apy repo in $HOME/git/apy
I installed apy with pip install -e .
After getting the error, I searched online and I found this similar issue: #1
Following the solution for the issue #1 I cloned the anki repo (https://github.com/ankitects/anki.git) in $HOME/git/anki
Then I tried the following:
export APY_ANKI_PATH=$HOME/git/anki
apy info
But I still get the same error as above.
Another useful info might be, that I recently changed my laptop. On the previous one, I could use apy without problems (it was really useful!). But I don't remember what I did when I installed it the first time on my old laptop. The only difference is the OS version: on the old one I had Linux Mint 20, on the new one I have Linux Mint 21
Could you help me? Thank you in advance!
This import is the problem.
Line 9 in 0552b91
I had to remove that import (and it's usage on the file) because apy crashed at execution. It seems that the official Anki's source code changed and anki.sync.py
file doesn't include "RemoteServer" anymore.
#https://github.com/ankitects/anki/blob/master/pylib/anki/sync.py
#This is all the code that's included inside anki.sync.py file
from .httpclient import HttpClient
AnkiRequestsClient = HttpClient
class Syncer:
def sync(self) -> str:
pass
I don't know if this is a related issue, or even expected behavior, but after executing
apy info
Anki throws the next error at restart.
What do you think? This is related to that conflicting import?
There's this PEP 668. It's meant to stop people using pip to put stuff into the system Python environment and interfering with the distro package manager. It's probably a good thing for the Python ecosystem on net, because so much stuff gets silently broken when people do this. But I think it will cause problems for apy, at least the way it's set up now. The install instructions say to pip install it into the system Python environment so it can import the anki
module, but that will no longer work when distributions adopt this PEP. Debian is already doing it, see here.
Not sure what the solution would be other than mandating a virtual environment install. Perhaps resurrect the pipx idea from issue #20? pipx is apparently recommended and maintained by the Python Packaging Authority nowadays, so this could be the way forward.
Fresh install of Ubuntu 22.04
With Anki 2.1.52-linux-qt6
APY latest version installed
apy -b /home/docker/.local/share/Anki2/
Config file: Not found
Collecton path: /home/docker/.local/share/Anki2/edyn/collection.anki2
Traceback (most recent call last):
File "/home/docker/.local/bin/apy", line 8, in <module>
sys.exit(main())
File "/usr/lib/python3/dist-packages/click/core.py", line 1128, in __call__
return self.main(*args, **kwargs)
File "/usr/lib/python3/dist-packages/click/core.py", line 1053, in main
rv = self.invoke(ctx)
File "/usr/lib/python3/dist-packages/click/core.py", line 1637, in invoke
super().invoke(ctx)
File "/usr/lib/python3/dist-packages/click/core.py", line 1395, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/usr/lib/python3/dist-packages/click/core.py", line 754, in invoke
return __callback(*args, **kwargs)
File "/usr/lib/python3/dist-packages/click/decorators.py", line 26, in new_func
return f(get_current_context(), *args, **kwargs)
File "/home/docker/.local/lib/python3.10/site-packages/apy/cli.py", line 54, in main
ctx.invoke(info)
File "/usr/lib/python3/dist-packages/click/core.py", line 754, in invoke
return __callback(*args, **kwargs)
File "/home/docker/.local/lib/python3.10/site-packages/apy/cli.py", line 183, in info
click.echo(f"Scheduler version: {a.col.sched_ver()}")
AttributeError: '_Collection' object has no attribute 'sched_ver'. Did you mean: 'schedVer'?
Using the example shown here under number 3, I get:
My ~/.config/apy/apy.json
:
{
"base": "/home/danj/.local/share/Anki2/",
"profile": "danj",
"query": "tag:leech",
"presets": {
"default": { "model": "Custom", "tags": ["marked"] }
},
"pngCommands": [
["latex", "-interaction=nonstopmode", "tmp.tex"],
[
"dvipng",
"-D",
"150",
"-T",
"tight",
"-bg",
"Transparent",
"tmp.dvi",
"-o",
"tmp.png"
]
],
"svgCommands": [
["lualatex", "-interaction=nonstopmode", "tmp.tex"],
["pdfcrop", "tmp.pdf", "tmp.pdf"],
["pdf2svg", "tmp.pdf", "tmp.svg"]
]
}
apy 0.9.0
anki 2.1.15
void linux 5.15.16_1
β― apy add -t python -d Python -m Basic-22px
reverting to stock json
Traceback (most recent call last):
File "/home/dufferzafar/.local/bin/apy", line 11, in <module>
load_entry_point('apy==0.1', 'console_scripts', 'apy')()
File "/usr/lib/python3.8/site-packages/pkg_resources/__init__.py", line 490, in load_entry_point
return get_distribution(dist).load_entry_point(group, name)
File "/usr/lib/python3.8/site-packages/pkg_resources/__init__.py", line 2859, in load_entry_point
return ep.load()
File "/usr/lib/python3.8/site-packages/pkg_resources/__init__.py", line 2450, in load
return self.resolve()
File "/usr/lib/python3.8/site-packages/pkg_resources/__init__.py", line 2456, in resolve
module = __import__(self.module_name, fromlist=['__name__'], level=0)
File "/home/dufferzafar/.local/lib/python3.8/site-packages/apy/cli.py", line 5, in <module>
from apy.anki import Anki
File "/home/dufferzafar/.local/lib/python3.8/site-packages/apy/anki.py", line 10, in <module>
from anki.sync import Syncer, MediaSyncer, RemoteServer, RemoteMediaServer
ImportError: cannot import name 'MediaSyncer' from 'anki.sync' (/usr/lib/python3.8/site-packages/anki/sync.py)
Haven't debugged this further. Perhaps anki has been updated? Not sure.
I tried out the new code to test the multiple deck support. I found that here:
Lines 55 to 60 in ec22f79
{**dict1, **dict2}
expression. I think a fix would be to switch the dictionaries in the expression:
defaults = {**{"markdown": True, "model": "Basic", "tags": "marked"}, **defaults}
How can I use latex math formulas within fields for apy add
and apy add-from-file
.
I could not find a recommended/working way in the documentation.
However, writing formulas with latex is for many anki users an important feature during card creation.
Hi,
My goal was to install Anki-2.1.26
and apy-0.6.0
on my system python (brew install [email protected]
), and saving a venv to at least know the python requirements/dependencies that got everything working.
I downloaded the Anki-source code, instead of cloning the git repo and checking out the 2.1.26 version. Which looked like this.
ββΈ~/bin/anki-2.1.26
ββΈβ―β―β― ls
. README.contributing pylib
.. README.development qt
.gitattributes README.md rslib
.github dist rspy
.gitignore meta run
CONTRIBUTORS pkgkey.asc scripts
LICENSE proto
Makefile pyenv
Note that ~/bin
is on my path. After installing all dependencies and sorting out errors from running the ~/bin/anki-2.1.26/run
executable, my pip3 list
looks similar to
ββΈ~/bin/anki-2.1.26
ββΈβ―β―β― pip3 list
Package Version
-------------- ---------
ankirspy 2.1.26
appdirs 1.4.4
attrs 20.1.0
beautifulsoup4 4.9.1
certifi 2020.6.20
chardet 3.0.4
click 7.1.2
decorator 4.4.2
distlib 0.3.1
filelock 3.0.12
idna 2.10
jsonschema 3.2.0
Markdown 3.2.2
pip 20.1.1
protobuf 3.13.0
PyQt5 5.15.0
PyQt5-sip 12.8.0
PyQtWebEngine 5.15.0
pyrsistent 0.16.0
readchar 2.0.1
requests 2.24.0
Send2Trash 1.5.0
setuptools 49.2.0
six 1.15.0
soupsieve 2.0.1
urllib3 1.25.10
virtualenv 20.0.31
wheel 0.34.2
Next I cloned and installed apy-0.6.0
, by using pip3 install .
inside the apy directory, adding it to the list above. I also configured the apy paths to
ββΈ~/bin/anki-2.1.26
ββΈβ―β―β― env | grep APY
APY_CONFIG=/Users/mikevink/.dotfiles/apy/apy.json
APY_ANKI_PATH=/Users/mikevink/bin/anki-2.1.26
ββΈ~/bin/anki-2.1.26
ββΈβ―β―β― cat ~/.dotfiles/apy/apy.json
{
"base": "/Users/mikevink/Library/Application Support/Anki2"
}
Launching Anki with ~/bin/anki-2.1.26/run
works fine at this point, but note that which anki
gives anki not found, and when I run apy I get
ββΈ~/bin/anki-2.1.26
ββΈβ―β―β― apy
Traceback (most recent call last):
File "/usr/local/bin/apy", line 5, in <module>
from apy.cli import main
File "/usr/local/lib/python3.8/site-packages/apy/cli.py", line 8, in <module>
from apy.anki import Anki
File "/usr/local/lib/python3.8/site-packages/apy/anki.py", line 8, in <module>
import anki
ModuleNotFoundError: No module named 'anki'
My "solution" so far is to set APY_ANKI_PATH=/Users/mikevink/bin/anki-2.1.26/pylib
and PYTHONPATH=/Users/mikevink/Documents/python:/Users/mikevink/bin/anki-2.1.26/qt
ββΈ~/bin/anki-2.1.26
ββΈβ―β―β― env | grep anki-2.1.26/
APY_ANKI_PATH=/Users/mikevink/bin/anki-2.1.26/pylib
PYTHONPATH=/Users/mikevink/Documents/python:/Users/mikevink/bin/anki-2.1.26/qt
And now apy and Anki works on my fresh system python! Thanks, I have been enjoying it a lot, I also made this reddit post, and if it is oke with you I would like to make a follow up post showing apy in action!
Hope I didn't do something stupid since I'm a little bit of a noob, in any case this shows that currently everything works on Mac/osx, maybe I could make a pull request for building Anki and apy on Mac.
Running apy --info
works. So all the anki
related modules have been loaded correctly.
But when running apy info
I get a config file not found error. Going through the readme, it felt as in the config file is optional. Is that not the case?
Config file: Not found
Traceback (most recent call last):
File "/home/dufferzafar/.local/bin/apy", line 11, in <module>
load_entry_point('apy==0.1', 'console_scripts', 'apy')()
File "/usr/lib/python3.8/site-packages/click/core.py", line 829, in __call__
return self.main(*args, **kwargs)
File "/usr/lib/python3.8/site-packages/click/core.py", line 782, in main
rv = self.invoke(ctx)
File "/usr/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "/usr/lib/python3.8/site-packages/click/core.py", line 1066, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/usr/lib/python3.8/site-packages/click/core.py", line 610, in invoke
return callback(*args, **kwargs)
File "/home/dufferzafar/.local/lib/python3.8/site-packages/apy/cli.py", line 147, in info
with Anki(cfg['base']) as a:
File "/home/dufferzafar/.local/lib/python3.8/site-packages/apy/anki.py", line 28, in __init__
self._init_load_collection(base, path)
File "/home/dufferzafar/.local/lib/python3.8/site-packages/apy/anki.py", line 47, in _init_load_collection
basepath = Path(base)
File "/usr/lib/python3.8/pathlib.py", line 1033, in __new__
self = cls._from_parts(args, init=False)
File "/usr/lib/python3.8/pathlib.py", line 674, in _from_parts
drv, root, parts = self._parse_args(args)
File "/usr/lib/python3.8/pathlib.py", line 658, in _parse_args
a = os.fspath(a)
TypeError: expected str, bytes or os.PathLike object, not NoneType
I am not able to add a single note with this command (as reported in the README):
apy add-single -p preset "Question/Front" "Answer/Back"
But if I run this, it works:
apy add-single --deck <deck_name> "Question/Front" "Answer/Back"
(Currently, I have just one profile and one deck)
Maybe the flag and the options have been changed and the README was not updated.
By the way, great library! I love it!
Various issues (#15 , #13 ) were talking about how to make this work in a virtual environment like pipx.
I've found a hacky workaround for this, but it involves editing the apy
source code yourself. I've done this with poetry so far and it seemed to work, I'll try it with pipx later.
Basically, wherever you have apy
installed, you need to find the cli.py
file and edit the top part so it looks like this instead:
"""A script to interact with the Anki database"""
import os
import click
# ultra-hacky way of getting this to work in a virtual environment
import sys, subprocess
command = "/usr/bin/python -c 'import sys; print(sys.path)'"
new_path = eval(subprocess.check_output(command, shell=True).decode("utf-8"))
# this means it will look on the system python path for imports first,
# then the virtual environment's original path
sys.path = new_path + sys.path
# the aqt_data_folder() function in aqt.utils depends on sys.prefix to
# find what it's looking for; sys.prefix depends on the interpreter used
# to launch the script, which means importing aqt will always fail in a
# virtual environment unless we override sys.prefix temporarily.
old_prefix = sys.prefix
sys.prefix = "/usr"
from apy.anki import Anki
from apy.config import cfg, cfg_file
sys.prefix = old_prefix
# rest of file continues here
You may need to edit the /usr/bin/python
part to whatever is the path of the interpreter that your Anki install is using, and the sys.prefix = /usr
part to whatever is two directories up from your aqt_data
folder (i.e. whatever corresponds to my /usr/share/aqt_data/
on your system).
It works:
[ckp95@ckp95-desktop apy-test]$ poetry run apy --help
reverting to stock json
Usage: apy [OPTIONS] COMMAND [ARGS]...
A script to interact with the Anki database.
The base directory may be specified with the -b / --base option. For
convenience, it may also be specified in the config file
`~/.config/apy/apy.json` or with the environment variable APY_BASE or
ANKI_BASE. This should point to the base directory where Anki stores it's
database and related files. See the Anki documentation for information
about where this is located on different systems
(https://apps.ankiweb.net/docs/manual.html#file-locations).
A few sub commands will open an editor for input. Vim is used by default.
The input is parsed when one saves and quits. To abort, one should exit
the editor with a non-zero exit code. In Vim, one can do this with the
`:cquit` command.
One may specify a different editor with the EDITOR environment variable.
For example, to use emacs one can add this to ones `~/.bashrc` (or
similar) file:
export EDITOR=emacs
Note: Use `apy subcmd --help` to get detailed help for a given subcommand.
Options:
-b, --base TEXT Set Anki base directory
-h, --help Show this message and exit.
Commands:
add Add notes interactively from terminal.
add-from-file Add notes from Markdown file.
check-media Check media
info Print some basic statistics.
list List cards that match a given query.
model Interact with Anki models.
review Review marked notes.
sync Synchronize collection with AnkiWeb.
tag List tags or add/remove tags from matching notes.
I'll leave this open in case anyone has a better way of doing this. Needless to say it's very hacky and could easily get broken by an update to either Anki or apy, so it's just a stopgap.
One of my biggest peeves with apy
is its implicit dependence on Qt. Basically, the code that lets you sync to Ankiweb relies on importing the ProfileManager
class, which comes from aqt
. And aqt
needs PyQT
to run. That's a huge pain in the ass to get working on some systems. For example, I just tried installing it again and got
Python 3.10.10 (main, Mar 5 2023, 22:26:53) [GCC 12.2.1 20230201] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import aqt
Running with temporary Qt5 compatibility shims.
Run with DISABLE_QT5_COMPAT=1 to confirm compatibility with Qt6.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/tmp/apy/.venv/lib/python3.10/site-packages/aqt/__init__.py", line 46, in <module>
from aqt import gui_hooks
File "/tmp/apy/.venv/lib/python3.10/site-packages/aqt/gui_hooks.py", line 11, in <module>
from _aqt.hooks import *
File "/tmp/apy/.venv/lib/python3.10/site-packages/_aqt/hooks.py", line 18, in <module>
from aqt.qt import QDialog, QEvent, QMenu, QModelIndex, QWidget, QMimeData
File "/tmp/apy/.venv/lib/python3.10/site-packages/aqt/qt/__init__.py", line 20, in <module>
from . import qt5_compat # needs to be imported first
File "/tmp/apy/.venv/lib/python3.10/site-packages/aqt/qt/qt5_compat.py", line 21, in <module>
import PyQt6.QtWebEngineCore
ModuleNotFoundError: No module named 'PyQt6.QtWebEngineCore'
In my experience there's always some crap like this where a bunch of dependencies for PyQT don't get installed right, and I waste hours debugging it, then forget what I did when it happens the next time months later.
And IMO it's silly for a purely CLI tool to need a GUI framework as a dependency.
I looked a little closer at the code, and unless I'm mistaken, all we need the ProfileManager
class for is to get a SyncAuth
object constructed from the profile data. That only relies on the syncKey
, currentSyncUrl
, customSyncUrl
, and networkTimeout
keys of the profile. The profile is stored as a pickle blob inside a sqlite file. So couldn't apy
just pull out all this data itself? That way we could sever the dependency on aqt
, and only rely on the anki
package.
I'm running Manjaro Linux. I installed apy
using pipx
. My anki is installed with pacman
, but the source wasn't in /usr/share/anki/
. Instead it looks as though it's in /usr/lib/python3.8/site-packages/
-- at least, there are directories for anki
, aqt
, and ankirspy
in there. So I do export APY_ANKI_PATH=/usr/lib/python3.8/site-packages/
However when I run apy --help
I get this output:
reverting to stock json
Traceback (most recent call last):
File "/home/ckp95/.local/bin/apy", line 5, in <module>
from apy.cli import main
File "/home/ckp95/.local/pipx/venvs/apy/lib/python3.8/site-packages/apy/cli.py", line 6, in <module>
from apy.anki import Anki
File "/home/ckp95/.local/pipx/venvs/apy/lib/python3.8/site-packages/apy/anki.py", line 10, in <module>
from aqt.profiles import ProfileManager
File "/usr/lib/python3.8/site-packages/aqt/__init__.py", line 35, in <module>
from aqt.main import AnkiQt # isort:skip
File "/usr/lib/python3.8/site-packages/aqt/main.py", line 21, in <module>
import aqt.mediasrv
File "/usr/lib/python3.8/site-packages/aqt/mediasrv.py", line 31, in <module>
_exportFolder = _getExportFolder()
File "/usr/lib/python3.8/site-packages/aqt/mediasrv.py", line 28, in _getExportFolder
raise Exception("couldn't find web folder")
Exception: couldn't find web folder
I dug around in the mediasrv.py
file and found this as the culprit function:
def _getExportFolder():
data_folder = aqt_data_folder()
webInSrcFolder = os.path.abspath(os.path.join(data_folder, "web"))
if os.path.exists(webInSrcFolder):
return webInSrcFolder
elif isMac:
dir = os.path.dirname(os.path.abspath(__file__))
return os.path.abspath(dir + "/../../Resources/web")
else:
raise Exception("couldn't find web folder")
_exportFolder = _getExportFolder()
I stuck a print(data_folder)
debug statement in there and ran apy --help
again, this is what I got:
/usr/lib/python3.8/site-packages/aqt/../aqt_data
Any idea how to fix?
I make a file called stuff.md
model: Basic
deck: All
# Note
## Front
sphinx of black quartz
## Back
judge my vow!
I add it to Anki using
apy add-from-file stuff.md
this gives:
reverting to stock json
Added note to deck: All
Review added notes? [y/N]: n
Database was modified.
Remember to sync!
When I open my Anki database, the note appears but it has the "marked" tag, which I didn't specify and don't want.
This doesn't happen when I use apy add
and use the interactive mode.
I'm sorry to intrude in the issues section of the project, but I couldn't find a more appropriate public space to ask
I've been recently interested in the possibility of parsing my notes into Anki cards to auto-generate them directly from text files (e.g. read an "historynotes.md" file, and generate Cloze cards for every date in the file), and I'd like to implement this in the same spirit as this utility. Would such an addition fit into the project, or if not, is there any way to use some parts of project directly to add this utility?
Thanks for your great work, this has been really useful in scripting, keep up the great work!
Can we add a r: Reposition card
option to the apy editor ?
Reviewing note 1 of 5
c: Continue e: Edit a: Add new d: Delete
m: Toggle markdown *: Toggle marked z: Toggle suspend p: Toggle pprint
F: Clear flags C: Show card names f: Show images E: Edit CSS
s: Save and stop x: Abort r: Reposition card
This is the equivalent of:
If I clicked OK, that would reposition the card from no. 13 to no. 1, and shift the position of other cards.
This is a feature that I use daily, by modifying a pre-existing deck as I study Japanese kanjis with a book.
I realized that there is apparently still a problem with the selection of the note type.
Let's say, I open anki desktop and add a card of note type "A" to a deck and close it.
Then I try to add a new card with apy add
or apy add-from-file
of a different note type "B".
Here, if the note types have a different number of fields, the following error will occur (if they have the same number of fields, still the old note type "A" will be chosen and populated with the chosen field values):
Traceback (most recent call last):
File "/home/skervim/anaconda3/envs/rl/bin/apy", line 11, in <module>
load_entry_point('apy', 'console_scripts', 'apy')()
File "/home/skervim/anaconda3/envs/rl/lib/python3.7/site-packages/click/core.py", line 764, in __call__
return self.main(*args, **kwargs)
File "/home/skervim/anaconda3/envs/rl/lib/python3.7/site-packages/click/core.py", line 717, in main
rv = self.invoke(ctx)
File "/home/skervim/anaconda3/envs/rl/lib/python3.7/site-packages/click/core.py", line 1137, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "/home/skervim/anaconda3/envs/rl/lib/python3.7/site-packages/click/core.py", line 956, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/home/skervim/anaconda3/envs/rl/lib/python3.7/site-packages/click/core.py", line 555, in invoke
return callback(*args, **kwargs)
File "/home/skervim/apy/apy/cli.py", line 63, in add
notes = a.add_notes_with_editor(tags, model, deck)
File "/home/skervim/apy/apy/anki.py", line 322, in add_notes_with_editor
return self.add_notes_from_file(tf.name)
File "/home/skervim/apy/apy/anki.py", line 328, in add_notes_from_file
tags)
File "/home/skervim/apy/apy/anki.py", line 356, in add_notes_from_list
note.get('deck')))
File "/home/skervim/apy/apy/anki.py", line 389, in _add_note
return Note(self, note)
File "/home/skervim/apy/apy/note.py", line 11, in __init__
self.fields = [x for x, y in self.n.items()]
File "/home/skervim/anki/anki/notes.py", line 89, in items
for ord, f in sorted(self._fmap.values())]
File "/home/skervim/anki/anki/notes.py", line 89, in <listcomp>
for ord, f in sorted(self._fmap.values())]
IndexError: list index out of range
Unfortunately, I couldn't find in the code why this happens, but I assume that chosen note type doesn't successfully override the last active note type and so apy
tries to populate the last note type instead of the chosen one.
hello I am absolute beginner to terminal and the coding stuff but I did paste the code:
git clone https://github.com/lervag/apy.git
pip install -e .
I downloaded something along the lines of "Command user line tools" which took a bit of time to download. I was suspecting it would install Xcode but now I now it has downloaded the "command line tool for Xcode". and then I pasted the thing same thing again and it downloaded apy.
I see the "apy" folder in MacintoshHD so I know apy is installed. I am typing apy --help
in terminal but its saying "command not found : apy" . Do I need to use bash or zsh to run this command and why is it saying "command not found : apy"?
EDIT: I ran the pip3 install --user thegithublink and it installed Markdown, reacher, soup sieve etc which I don't have any idea about.
I was hoping someone would guide me on how to get this working. Thank you.
I really, really like this project and would like to contribute to it. It's such a simple tool, but it has revolutionized the way I interact with Anki.
I have some ideas about what to improve, but would like to get your feedback first, @lervag.
Do you think these make sense? Let me know if you have other ideas.
Looking forward to contributing!
Whenever I start using a new piece of software, I usually have a bunch of design related issues. As in, what would I do differently if I were making it. I'm just opening this issue to be a "catch-all" place for such things. Will keep adding comments as I use apy
Hi Karl, got this working with my setup the other day and enjoy it so far, thanks.
I tried implementing the adding cards via Vim example you wrote up in the Wiki. It basically worked, I was wondering if it was possible to control which deck it added the cards to? I tried running it twice as a test, each time it added my cards to different deck.
I have a problem with the markdown extension:
data-original-markdown
attribute to the containing elementThe only solution I've found at this point is to stop using the markdown plugin, but it appears that you add it by default for Basic cards:
Line 357 in e51eac8
apy.json
?
The default could be something like:
{
"markdown_models": ["Basic"]
}
In my case, I'd set "markdown_models": []
Edit: Checking again on the options, the presets
config could be extended to enable/disable markdown as well
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.