Created
December 24, 2020 06:14
-
-
Save armstnp/5722e968cc58a28fa18385c9f2b43d14 to your computer and use it in GitHub Desktop.
Scriptable: GW2 Guild Widget
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ############# | |
// ## Setup ## | |
// ############# | |
// Step 1: Fill in your API key, or leave blank to use none; this will only present public info. | |
// - Don't have an API key? Create one! https://wiki.guildwars2.com/wiki/API:API_key | |
// - API key must have 'account' and 'guild' permissions for full guild access. | |
// - The key must belong to the guild owner account for full guild access. | |
const API_KEY = ''; | |
// Step 2: Fill in the Guild ID; find by going to | |
// https://api.guildwars2.com/v2/guild/search?name=<exact guild name> | |
const GUILD_ID = 'F9799071-D60B-407B-8A75-A5EEC3120516'; // Example ID filled in | |
// ################### | |
// ## Main Script ## | |
// ################### | |
let [size, useKey] = await getConfig(API_KEY); | |
const apiKey = useKey ? API_KEY : ''; | |
let guild = await loadGuild(apiKey, GUILD_ID); | |
let widget; | |
if('error' in guild) { | |
widget = buildErrorWidget('Failed to fetch guild!'); | |
} else { | |
const log = await loadLog(guild, size); | |
const icons = await loadIcons(guild, size); | |
widget = await { | |
small: { basic: buildSmallBasicWidget, detailed: buildSmallDetailedWidget }, | |
medium: { basic: buildMediumBasicWidget, detailed: buildMediumDetailedWidget }, | |
large: { basic: buildLargeBasicWidget, detailed: buildLargeDetailedWidget } | |
}[size][guild['depth']](guild, log, icons); | |
} | |
if (config.runsInWidget) { | |
Script.setWidget(widget); | |
} else { | |
switch(size) { | |
case 'small': | |
widget.presentSmall(); | |
break; | |
case 'medium': | |
widget.presentMedium(); | |
break; | |
case 'large': | |
widget.presentLarge(); | |
break; | |
} | |
} | |
Script.complete(); | |
// ######################### | |
// ## Top-level Widgets ## | |
// ######################### | |
function buildSmallBasicWidget(guild, log, icons) { | |
let widget = new ListWidget(); | |
addFullGuildName(widget, guild); | |
assignEmblemBg(widget, icons); | |
return widget; | |
} | |
function buildSmallDetailedWidget(guild, log, icons) { | |
let widget = buildSmallBasicWidget(guild, log, icons); | |
widget.addSpacer(10); | |
addGuildLevel(widget, guild); | |
return widget; | |
} | |
function buildMediumBasicWidget(guild, log, icons) { | |
let widget = new ListWidget(); | |
let header = widget.addStack(); | |
header.layoutHorizontally(); | |
header.centerAlignContent(); | |
addGuildEmblem(header, icons); | |
addFullGuildName(header, guild); | |
assignGradientBg(widget); | |
return widget; | |
} | |
function buildMediumDetailedWidget(guild, log, icons) { | |
let widget = new ListWidget(); | |
addWideHeader(widget, guild, icons); | |
widget.addSpacer(15); | |
addResources(widget, guild, icons); | |
assignGradientBg(widget); | |
return widget; | |
} | |
async function buildLargeBasicWidget(guild, log, icons) { | |
let widget = new ListWidget(); | |
let stack = widget.addStack(); | |
stack.layoutVertically(); | |
stack.centerAlignContent(); | |
addGuildEmblem(stack, icons); | |
stack.addSpacer(15); | |
addFullGuildName(stack, guild); | |
assignGradientBg(widget); | |
return widget; | |
} | |
function buildLargeDetailedWidget(guild, log, icons) { | |
let widget = new ListWidget(); | |
let stack = widget.addStack(); | |
stack.layoutVertically(); | |
stack.centerAlignContent(); | |
addFullGuildName(stack, guild); | |
stack.addSpacer(10); | |
addWideResources(stack, guild, icons); | |
stack.addSpacer(30); | |
addLog(stack, log, icons); | |
assignGradientBg(widget); | |
return widget; | |
} | |
function buildErrorWidget(message) { | |
let widget = new ListWidget(); | |
let error = widget.addText(message); | |
error.font = Font.heavySystemFont(); | |
error.textColor = Color.red(); | |
error.centerAlignText(); | |
return widget; | |
} | |
// ################## | |
// ## Components ## | |
// ################## | |
function addWideHeader(container, guild, icons) { | |
let header = container.addStack(); | |
header.layoutHorizontally(); | |
header.centerAlignContent(); | |
addGuildEmblem(header, icons); | |
addFullGuildName(header, guild); | |
header.addSpacer(10); | |
addGuildLevel(header, guild); | |
} | |
function addGuildEmblem(header, icons) { | |
let emblem = header.addImage(icons.emblem); | |
emblem.containerRelativeShape = true; | |
} | |
function addFullGuildName(container, guild) { | |
let fullName = `${guild['name']} [${guild['tag']}]`; | |
let text = container.addText(fullName); | |
text.font = Font.headline(); | |
text.textColor = Color.white(); | |
text.shadowColor = Color.gray(); | |
text.shadowOffset = new Point(-2, 2); | |
text.shadowRadius = 3; | |
text.centerAlignText(); | |
text.minimumScaleFactor = 0.75; | |
} | |
function addGuildLevel(container, guild) { | |
let text = container.addText(`Level ${guild['level']}`); | |
text.font = Font.subheadline(); | |
text.textColor = new Color('BBB'); | |
text.centerAlignText(); | |
} | |
function addResources(container, guild, icons) { | |
let stack = container.addStack(); | |
stack.layoutHorizontally(); | |
stack.centerAlignContent(); | |
stack.setPadding(0, 25, 0, 25); | |
addAetherium(stack, guild, icons); | |
stack.addSpacer(50); | |
addFavor(stack, guild, icons); | |
} | |
function addWideResources(container, guild, icons) { | |
let hstack = container.addStack(); | |
hstack.layoutHorizontally(); | |
hstack.centerAlignContent(); | |
addGuildEmblem(hstack, icons); | |
hstack.addSpacer(15); | |
let vstack = hstack.addStack(); | |
vstack.layoutVertically(); | |
vstack.centerAlignContent(); | |
addAetherium(vstack, guild, icons); | |
vstack.addSpacer(20); | |
addFavor(vstack, guild, icons); | |
} | |
function addAetherium(container, guild, icons) { | |
let stack = container.addStack(); | |
stack.layoutHorizontally(); | |
let icon = stack.addImage(icons.aetherium); | |
icon.resizable = false; | |
let text = stack.addText(guild['aetherium'].toString()); | |
text.font = Font.thinSystemFont(26); | |
text.textColor = new Color('60A0D0'); | |
} | |
function addFavor(container, guild, icons) { | |
let stack = container.addStack(); | |
stack.layoutHorizontally(); | |
let icon = stack.addImage(icons.favor); | |
icon.resizable = false; | |
let text = stack.addText(guild['favor'].toString()); | |
text.font = Font.thinSystemFont(26); | |
text.textColor = new Color('D0D040'); | |
} | |
function addLog(container, log, icons) { | |
let stack = container.addStack(); | |
stack.layoutVertically(); | |
log.slice(0, 5).forEach(entry => addLogEntry(stack, entry, icons)); | |
} | |
function addLogEntry(container, entry, icons) { | |
let stack = container.addStack(); | |
stack.layoutHorizontally(); | |
stack.centerAlignContent(); | |
let icon, message; | |
switch(entry['type']) { | |
case 'joined': | |
icon = icons.joined; | |
message = `${entry['user']} joined`; | |
break; | |
case 'invited': | |
icon = icons.invited; | |
message = `${entry['user']} invited by ${entry['invited_by']}`; | |
break; | |
case 'kick': | |
icon = icons.kicked; | |
message = `${entry['user']} kicked by ${entry['kicked_by']}`; | |
break; | |
case 'rank_change': | |
icon = icons.rankChanged; | |
message = `${entry['user']} made ${entry['new_rank']} by ${entry['changed_by']}`; | |
break; | |
case 'treasury': | |
icon = icons.treasury; | |
message = `${entry['user']} deposited to treasury`; | |
break; | |
case 'stash': | |
switch(entry['operation']) { | |
case 'deposit': | |
icon = icons.stashDeposit; | |
message = `${entry['user']} deposited to stash`; | |
break; | |
case 'withdraw': | |
icon = icons.stashWithdraw; | |
message = `${entry['user']} withdrew from stash`; | |
break; | |
case 'move': | |
icon = icons.stashMove; | |
message = `${entry['user']} moved items in the stash`; | |
break; | |
} | |
break; | |
case 'motd': | |
icon = icons.motd; | |
message = `${entry['user']} changed the MOTD`; | |
break; | |
case 'upgrade': | |
switch(entry['action']) { | |
case 'queued': | |
icon = icons.upgradeQueued; | |
message = 'user' in entry ? `${entry['user']} queued an upgrade` : 'Upgrade queued'; | |
break; | |
case 'cancelled': | |
icon = icons.upgradeCancelled; | |
message = `${entry['user']} cancelled an upgrade`; | |
break; | |
case 'completed': | |
icon = icons.upgradeCompleted; | |
message = `${entry['user']} completed an upgrade`; | |
break; | |
case 'sped_up': | |
icon = icons.upgradeSpedUp; | |
message = 'Upgrade completed'; | |
break; | |
} | |
break; | |
} | |
let image = stack.addImage(icon); | |
image.tintColor = Color.cyan(); | |
stack.addSpacer(5); | |
let text = stack.addText(message); | |
text.font = Font.lightRoundedSystemFont(12); | |
container.addSpacer(3); | |
} | |
function assignGradientBg(container) { | |
let bg = new LinearGradient(); | |
bg.colors = [Color.black(), Color.black(), new Color('002244')]; | |
bg.locations = [0.0, 0.4, 1.4]; | |
bg.startPoint = new Point(0.2, 0); | |
bg.endPoint = new Point(0.8, 1); | |
container.backgroundGradient = bg; | |
} | |
function assignEmblemBg(widget, icons) { | |
widget.backgroundImage = icons.emblem; | |
} | |
// ############# | |
// ## Pulls ## | |
// ############# | |
async function loadGuild(apiKey, guildId) { | |
const url = `https://api.guildwars2.com/v2/guild/${guildId}?access_token=${apiKey}`; | |
let guild = await new Request(url).loadJSON(); | |
guild['depth'] = 'level' in guild ? 'detailed' : 'basic'; | |
return guild; | |
} | |
async function loadLog(guild, size) { | |
if(guild['depth'] !== 'detailed' || size !== 'large') { return []; } | |
const url = `https://api.guildwars2.com/v2/guild/${guild['id']}/log?access_token=${apiKey}`; | |
return await new Request(url).loadJSON(); | |
} | |
async function loadIcons(guild, size) { | |
let icons = { emblem: await loadLargeGuildEmblem(guild) }; | |
if(guild['depth'] === 'basic') { return icons; } | |
icons.aetherium = await loadAetheriumIcon(); | |
icons.favor = await loadFavorIcon(); | |
if(size === 'large') { | |
icons.joined = SFSymbol.named('person.badge.plus.fill').image; | |
icons.kicked = SFSymbol.named('person.badge.minus.fill').image; | |
icons.invited = SFSymbol.named('envelope.circle').image; | |
icons.rankChanged = SFSymbol.named('tag.fill').image; | |
icons.treasury = SFSymbol.named('tray.and.arrow.down.fill').image; | |
icons.stashDeposit = SFSymbol.named('icloud.and.arrow.up.fill').image; | |
icons.stashWithdraw = SFSymbol.named('icloud.and.arrow.down.fill').image; | |
icons.stashMove = SFSymbol.named('arrow.clockwise.icloud.fill').image; | |
icons.motd = SFSymbol.named('quote.bubble.fill').image; | |
icons.upgradeQueued = SFSymbol.named('play.circle').image; | |
icons.upgradeCancelled = SFSymbol.named('stop.circle').image; | |
icons.upgradeCompleted = SFSymbol.named('checkmark.seal.fill').image; | |
icons.upgradeSpedUp = SFSymbol.named('forward.fill').image; | |
} | |
return icons; | |
} | |
async function loadLargeGuildEmblem(guild) { | |
const url = `https://data.gw2.fr/guild-emblem/name/${guild['name'].replace(/ /g, "%20")}.png`; | |
return await new Request(url).loadImage(); | |
} | |
async function loadAetheriumIcon() { | |
const url = 'https://wiki.guildwars2.com/images/archive/2/23/20161125172412%21Aetherium.png'; | |
return await new Request(url).loadImage(); | |
} | |
async function loadFavorIcon() { | |
const url = 'https://wiki.guildwars2.com/images/archive/0/00/20171101170734%21Favor.png'; | |
return await new Request(url).loadImage(); | |
} | |
async function getConfig(existingKey) { | |
const size = config.runsInWidget | |
? config.widgetFamily | |
: await askSize(); | |
const useKey = config.runsInWidget || await askAuth(existingKey); | |
return [size, useKey]; | |
} | |
async function askSize() { | |
const choices = ['Small', 'Medium', 'Large']; | |
let prompt = new Alert(); | |
prompt.title = 'Select Preview Size'; | |
choices.forEach(c => prompt.addAction(c)); | |
var choice = await prompt.present(); | |
if(choice === -1) { return null; } | |
return choices[choice].toLowerCase(); | |
} | |
async function askAuth(existingKey) { | |
if(existingKey.length === 0) { return true; } | |
const prompt = new Alert(); | |
prompt.title = 'Use Auth Key?'; | |
prompt.addAction('Yes'); | |
prompt.addAction('No'); | |
const choice = await prompt.present(); | |
return choice == 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Small + Public
Small + Full
Medium + Public
Medium + Full
Large + Public
(Alignment is tricky in Scriptable.)
Large + Full