德国码农开发抢厕纸神器,再也不用愁了

,点击 欧盟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非常简单。有兴趣和时间的同学可以将其迅速移植到安卓系统的小组件里,造福处于水生火热中的德国人民。

本月新闻&文章回顾

可向下滑动

美企起薪过高不讲武德,德国政客劝其耗子尾汁

2021.02新闻&文章回顾

2021.01新闻&文章回顾

2020全年新闻&文章回顾

点了在看,年薪百万

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值