NEW UPDATED VERSION: iOS auto updating kanji wallpaper (new and improved)
Hello! I recently created an auto updating wallpaper to show my current progress on the wanikani kanji, using the iOS app Scriptable and Siri Shortcuts
Right now the numbers are hard coded to iPhone XS Max resolution, but they should be easy to adjust.
I have it set to run the script every time I close my wanikani app. It keeps a cache and doesn’t request the whole payload every time, but if you need to force update it you can remove the “//“ from “ // override = true” and run the shortcut (remember to set it back once you’re done)
https://www.icloud.com/shortcuts/de3e9ec2f6244b4fa608e4564a15d517
To add the shortcut:
1: click the link (it should open in shortcuts)
2: scroll to the bottom and click “add untrusted shortcut”
3: it should ask you for your wanikani api token, get it from the website and paste it in (Do NOT click the green plus on the left. Just paste the token in)
4: click done
Once the shortcut is added, you can set up the auto reloading like so:
1: click the automation tab in the shortcuts app
2: click the plus in the upper right hand corner
3: click “personal automation” (this may not appear if you do not have a home set up through apple. If it doesn’t ask then it should default to personal and you can continue)
4: select “App” from the available options
5: choose your app and select “is closed”
6: click add action
7: search for “run shortcut” and select the new shortcut
8: click next, then done
Hope this newer version is simpler to set up, and the instructions are fully clear
OLD VERSION:
Summary
Scriptable script:
const headers = {"Authorization": "Bearer <WANIKANI_API_TOKEN>"};
const cache_filename = "wk_wallpaper_data.json";
const dimensions = {
width: 1418,
height: 3072,
top_buffer: 192 + 132,
bottom_buffer: 192 + 102,
left_buffer: 88,
right_buffer: 88,
}
const colors = [
"303030",
"DD0093",
"DD0093",
"DD0093",
"DD0093",
"882D8A",
"882D8A",
"294DDB",
"0093DD",
"FAAC05"
]
async function get_wk_data() {
let fm = FileManager.local()
let path = fm.joinPath(fm.cacheDirectory(), cache_filename)
let override = false
// override = true
if(!fm.fileExists(path) || override) {
let url = "https://api.wanikani.com/v2/subjects?types=kanji";
kanji = {}
while(url) {
let request = new Request(url);
request.headers = headers;
let response = await request.loadJSON();
url = response.pages.next_url;
response.data.forEach((new_kanji) => {
kanji[new_kanji.data.characters] = new_kanji.id
});
}
url = 'https://api.wanikani.com/v2/assignments?subject-types=kanji'
srs = {}
while(url) {
let request = new Request(url);
request.headers = headers;
let response = await request.loadJSON();
url = response.pages.next_url;
response.data.forEach((new_item) => {
srs[new_item.data.subject_id] = new_item.data.srs_stage;
});
}
let wk_data = {
kanji,
srs
}
let image_params = await calculate_image_params(wk_data)
let cache_data = {
updated_time: new Date().toISOString(),
wk_data,
image_params,
}
await fm.writeString(path, JSON.stringify(cache_data))
return cache_data
} else {
let cache_data = await JSON.parse(fm.readString(path))
url = 'https://api.wanikani.com/v2/assignments?subject-types=kanji&updated_after=' + cache_data.updated_time
let updated = false
while(url) {
let request = new Request(url);
request.headers = headers;
let response = await request.loadJSON();
url = response.pages.next_url;
response.data.forEach((new_item) => {
let id = new_item.data.subject_id
let old_srs = cache_data.wk_data.srs[id]
let new_srs = new_item.data.srs_stage
if(colors[old_srs] != colors[new_srs]) {
updated = true
}
cache_data.wk_data.srs[id] = new_srs;
});
}
cache_data.updated_time = new Date().toISOString()
await fm.writeString(path, JSON.stringify(cache_data))
if(!updated) {
return
}
return cache_data
}
}
async function calculate_image_params(wk) {
const w = dimensions.width - dimensions.left_buffer - dimensions.right_buffer
const h = dimensions.height - dimensions.top_buffer - dimensions.bottom_buffer
potential_s = []
let r = 1
let n = Object.keys(wk.kanji).length
// n = 14269
for(let i = 1; i <= w; i++) {
potential_s.push(w / i)
}
for(let j = 1; j <= h; j++) {
potential_s.push(h / j / r)
}
potential_s.sort((a, b) => b - a)
let s
let rows
let cols
let x_offset
let y_offset
for(let i = 0; i < potential_s.length; i++) {
s = potential_s[i]
cols = Math.floor(w / s)
rows = Math.floor(h / r / s)
if(rows * cols < n) {
continue
}
x_offset = (w - cols * s) / 2
y_offset = (h - rows * r * s) / 2
break
}
return {
adjusted_width: w,
adjusted_height: h,
rows,
cols,
x_offset,
y_offset,
s,
r,
}
}
async function get_image_data(
data,
dim,
colors
) {
let cx = new DrawContext()
cx.size = new Size(dimensions.width, dimensions.height)
cx.opaque = true
cx.setFillColor(Color.black())
cx.fill(new Rect(0, 0, dimensions.width, dimensions.height))
cx.setFont(Font.heavySystemFont(data.image_params.s))
cx.setTextAlignedCenter()
let cols = data.image_params.cols
let s = data.image_params.s
let r = data.image_params.r
let l_buff = dimensions.left_buffer + data.image_params.x_offset
let t_buff = dimensions.top_buffer + data.image_params.y_offset
let kanji = Object.keys(data.wk_data.kanji)
for(let i = 0; i < kanji.length; i++) {
let x = i % data.image_params.cols
let y = Math.floor(i / data.image_params.cols)
let k = kanji[i]
let id = data.wk_data.kanji[k]
let srs = data.wk_data.srs[id] || 0
let color = new Color(colors[srs])
let rect = new Rect(
l_buff + x * s,
t_buff + y * s * r,
s,
s * r,
)
cx.setTextColor(color)
cx.drawTextInRect(kanji[i], rect)
}
let image = cx.getImage()
return image
}
try {
let data = await get_wk_data()
if(data !== undefined) {
let image = await get_image_data(
data,
dimensions,
colors,
)
let raw_image = Data.fromPNG(image)
let base64_image = raw_image.toBase64String()
Script.setShortcutOutput(base64_image)
}
} finally {
Script.complete()
}
Shortcut:
Shortcuts