,点击 欧盟IT那些事 关注我们

公告:因企鹅审核规定,本公众号从《德国IT那些事》更名为《欧盟IT那些事》。
厕纸在手,天下我有!
疫情中由于欧美人民对厕纸的蜜汁喜爱,导致超市里的厕纸经常处于供不应求的状态。尽管超市已经限购每人每次购买厕纸的数量,可还是有很多小伙伴遇到买不到厕纸的尴尬情况。
为此一德国码农小哥Marco Dengel,急天下之急,忧天下之忧,开发了一个小脚本,显示周边DM超市厕纸的货量,来帮助你准确快速地抢厕纸。该脚本开源,请于文末获取链接。
德国人民为此激动不已,喜大普奔,该开源项目评论区充斥着 “楼主好人”,“楼主一生平安”等评论。各大主流媒体也纷纷报道该神器和使用方法,包括但不仅限于:
RTL, Macwelt, Business Punk, Berliner Zeitung, iPhone-ticker, t3n, Caschys Blog, RND, Giga, Computer Bild, Chip, Curved, Notebooksbilliger, Vowe, Billiger-Telefonieren
1安装条件
首先你要有一个iOS系统,iPhone或者iPad。
iOS 14 系统,低的版本不能安装。
Scriptable version 1.5+ (从App Store免费下载)
2获取Store ID
访问DM官网,打开 Shop Finder:
https://www.dm.de/store
输入邮政编码,找到你附近的DM超市,在地图上选中。点击“Weitere Details”。
这时浏览器的地址栏处会显示你所选择的DM超市的Store ID。如下图所示,地址栏处显示"https://www.dm.de/store/de-2449/koeln/hohenzollernring-58",那么Store ID则为2449。记住这个ID,之后将使用到。
3安装
打开之前安装好的Scriptable app。点击右上角的“+”号按钮添加如下脚本。如果你位于奥地利,请将第15行的de改成at。
// dm Klopapier Widget
//
// Copyright (C) 2020 by marco79 <marco79cgn@gmail.com>
//
// Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
// IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
// OF THIS SOFTWARE.
//
// Toilet paper icon made by boettges
let country = 'de' // für Österreich bitte 'at' verwenden
let storeId = 251
let param = args.widgetParameter
if (param != null && param.length > 0) {
storeId = param
}
const widget = new ListWidget()
const storeInfo = await fetchStoreInformation()
const storeCapacity = await fetchAmountOfPaper()
await createWidget()
// used for debugging if script runs inside the app
if (!config.runsInWidget) {
await widget.presentSmall()
}
Script.setWidget(widget)
Script.complete()
// build the content of the widget
async function createWidget() {
widget.addSpacer(4)
const logoImg = await getImage('dm-logo.png')
widget.setPadding(10, 10, 10, 10)
const titleFontSize = 12
const detailFontSize = 36
const logoStack = widget.addStack()
logoStack.addSpacer(86)
const logoImageStack = logoStack.addStack()
logoStack.layoutHorizontally()
logoImageStack.backgroundColor = new Color("#ffffff", 1.0)
logoImageStack.cornerRadius = 8
const wimg = logoImageStack.addImage(logoImg)
wimg.imageSize = new Size(40, 40)
wimg.rightAlignImage()
widget.addSpacer()
const icon = await getImage('toilet-paper.png')
let row = widget.addStack()
row.layoutHorizontally()
row.addSpacer(2)
const iconImg = row.addImage(icon)
iconImg.imageSize = new Size(40, 40)
row.addSpacer(13)
let column = row.addStack()
column.layoutVertically()
const paperText = column.addText("厕纸")
paperText.font = Font.mediumRoundedSystemFont(13)
const packageCount = column.addText(storeCapacity.toString())
packageCount.font = Font.mediumRoundedSystemFont(22)
if (storeCapacity < 30) {
packageCount.textColor = new Color("#E50000")
} else {
packageCount.textColor = new Color("#00CD66")
}
widget.addSpacer(4)
const row2 = widget.addStack()
row2.layoutVertically()
const street = row2.addText(storeInfo.address.street)
street.font = Font.regularSystemFont(11)
const zipCity = row2.addText(storeInfo.address.zip + " " + storeInfo.address.city)
zipCity.font = Font.regularSystemFont(11)
let currentTime = new Date().toLocaleTimeString('de-DE', { hour: "numeric", minute: "numeric" })
let currentDay = new Date().getDay()
let isOpen
if (currentDay > 0) {
const todaysOpeningHour = storeInfo.openingHours[currentDay-1].timeRanges[0].opening
const todaysClosingHour = storeInfo.openingHours[currentDay-1].timeRanges[0].closing
const range = [todaysOpeningHour, todaysClosingHour];
isOpen = isInRange(currentTime, range)
} else {
isOpen = false
}
let shopStateText
if (isOpen) {
shopStateText = row2.addText('Geöffnet')
shopStateText.textColor = new Color("#00CD66")
} else {
shopStateText = row2.addText('Geschlossen')
shopStateText.textColor = new Color("#E50000")
}
shopStateText.font = Font.mediumSystemFont(11)
}
// fetches the amount of toilet paper packages
async function fetchAmountOfPaper() {
let url
let counter = 0
if (country.toLowerCase() === 'at') {
// Austria
const array = ["156754", "180487", "194066", "188494", "194144", "273259", "170237", "232201", "170425", "283216", "205873", "205874", "249881", "184204"]
for (var i = 0; i < array.length; i++) {
let currentItem = array[i]
url = 'https://products.dm.de/store-availability/AT/products/dans/' + currentItem + '/stocklevel?storeNumbers=' + storeId
let req = new Request(url)
let apiResult = await req.loadJSON()
if (req.response.statusCode == 200) {
counter += apiResult.storeAvailability[0].stockLevel
}
}
} else {
// Germany
url = 'https://products.dm.de/store-availability/DE/availability?dans=595420,708997,137425,28171,485698,799358,863567,452740,610544,846857,709006,452753,879536,452744,485695,853483,594080,504606,593761,525943,842480,535981,127048,524535&storeNumbers=' + storeId
const req = new Request(url)
const apiResult = await req.loadJSON()
for (var i in apiResult.storeAvailabilities) {
counter += apiResult.storeAvailabilities[i][0].stockLevel
}
}
return counter
}
// fetches information of the configured store, e.g. opening hours, address etc.
async function fetchStoreInformation() {
let url
if (country.toLowerCase() === 'at') {
url = 'https://store-data-service.services.dmtech.com/stores/item/at/' + storeId
widget.url = 'https://www.dm.at/search?query=toilettenpapier&searchType=product'
} else {
url = 'https://store-data-service.services.dmtech.com/stores/item/de/' + storeId
widget.url = 'https://www.dm.de/search?query=toilettenpapier&searchType=product'
}
let req = new Request(url)
let apiResult = await req.loadJSON()
return apiResult
}
// checks whether the store is currently open or closed
function isInRange(value, range) {
return value >= range[0] && value <= range[1];
}
// get images from local filestore or download them once
async function getImage(image) {
let fm = FileManager.local()
let dir = fm.documentsDirectory()
let path = fm.joinPath(dir, image)
if (fm.fileExists(path)) {
return fm.readImage(path)
} else {
// download once
let imageUrl
switch (image) {
case 'dm-logo.png':
imageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/5/50/Dm_Logo.svg/300px-Dm_Logo.svg.png"
break
case 'toilet-paper.png':
imageUrl = "https://i.imgur.com/Uv1qZGV.png"
break
default:
console.log(`Sorry, couldn't find ${image}.`);
}
let iconImage = await loadImage(imageUrl)
fm.writeImage(path, iconImage)
return iconImage
}
}
// helper function to download an image from a given url
async function loadImage(imgUrl) {
const req = new Request(imgUrl)
return await req.loadImage()
}
// end of script
// bitte bis zum Ende kopieren
保存脚本如下图:
将该脚本重命名为“找厕纸”,或其它你喜爱的名字。可以点击右下角的右箭头预览最终效果。
进入iOS系统主屏幕,长按屏幕,出现“搜索小组件”界面。然后下滑列表选择“Scriptable“”
点击”添加小组件“按钮,你的手机屏幕上会出现一个新的空白的小组件。点击它,然后在第一栏里选择我们刚刚添加的脚本,名为”找厕纸“。在第三栏”Parameter“这里输入你刚刚记录下来的 Store ID。
大功告成!若无意外的话,你的手机屏幕里就会正确显示周围DM超市的厕纸存货了!
参考
项目地址: https://gist.github.com/marco79cgn/23ce08fd8711ee893a3be12d4543f2d2
可惜安卓系统还没有类似的小程序,不过该脚本由JS开发,逻辑和UI非常简单。有兴趣和时间的同学可以将其迅速移植到安卓系统的小组件里,造福处于水生火热中的德国人民。
本月新闻&文章回顾
可向下滑动
点了在看,年薪百万