Skip to content

Instantly share code, notes, and snippets.

@armstnp
Created December 24, 2020 06:14
Show Gist options
  • Save armstnp/5722e968cc58a28fa18385c9f2b43d14 to your computer and use it in GitHub Desktop.
Save armstnp/5722e968cc58a28fa18385c9f2b43d14 to your computer and use it in GitHub Desktop.
Scriptable: GW2 Guild Widget
// #############
// ## 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;
}
@armstnp
Copy link
Author

armstnp commented Dec 24, 2020

Small + Public

C1E2B97D-7341-4B42-9D30-4A6280D20404

Small + Full

48B96148-E85A-45F1-B5DF-F0B1B0AF3DBE

Medium + Public

04A392DA-F5D6-4894-8779-46040F3B735C

Medium + Full

231E3C1D-B348-4538-AFE2-E306D65C3055

Large + Public

B2F6E2E2-C45F-445A-A01D-4671CC99EA50

(Alignment is tricky in Scriptable.)

Large + Full

2856EACC-BBD7-4F67-93AA-3EFF5EF6F886

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment