הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

היום למדתי: כבר מזמן לא צריך להוסיף קו תחתי אחרי exec ב Qt for Python

02/03/2023

לפמעים צריך לחכות שמשהו יהפוך ל Deprecated עד שאני אשים לב שכבר הרבה זמן הוא לא היה הכרחי. זה לפחות היה הסיפור עם PyQt ו PySide והפקודה exec.

בפייתון 2 המילה exec היתה מילה שמורה. הפקודה exec של Qt היא זאת שמפעילה את הלולאה הראשית של היישום, והשם exec הוא בדיוק שם הפונקציה ב C++, לכן ובשביל לא לבלבל יותר מדי מתכנתים שכבר מכירים Qt מ C++, בפייתון בחרו לאמץ את השם אבל להוסיף _ בסוף. תוכנית PyQt פשוטה של שלום עולם לכן נראתה כך:

import sys
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QApplication, QLabel

if __name__ == "__main__":
    app = QApplication(sys.argv)
    label = QLabel("Hello World", alignment=Qt.AlignCenter)
    label.show()
    sys.exit(app.exec_())

אבל בפייתון 3 המילה exec כבר לא היתה מילה שמורה, ולכן החל מ Qt5 אפשר להשתמש באותו exec של C++. בגירסה 6 אנחנו כבר מקבלים Deprecation Warning על ה exec עם הקו התחתי בסוף. אגב גם על sys.exit שמופיע בתוכנית הדוגמה אפשר לוותר והקוד הבא יעשה בדיוק את אותו דבר ובלי להתלונן:

import sys
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QApplication, QLabel

if __name__ == "__main__":
    app = QApplication(sys.argv)
    label = QLabel("Hello World", alignment=Qt.AlignCenter)
    label.show()
    app.exec()

נ.ב. רוצים להפעיל אותו אצלכם? כל מה שצריך זה להתקין לפני את החבילות של PySide ואתם מסודרים:

pip install PySide6

טיפ גיטהאב: איך להדפיס את הלוג של ההרצה האחרונה דרך שורת הפקודה

01/03/2023

לגיטהאב יש כלי שורת פקודה שנקרא gh שהוא סוג של קסם, כי הוא מאפשר לעשות כל דבר שאפשר לעשות דרך האתר, משורת הפקודה ועם פלט JSON שמאוד קל לפענוח. בעבר כתבתי עליו בפוסט איך ליצור Pull Request משורת הפקודה והיום הוא חוזר בשביל לענות על עוד שאלה מתלמיד - איך להדפיס את הלוג של ההרצה האחרונה של Github Action משורת הפקודה? ופה עלינו בסך הכל לשלב שתי פקודות.

הפקודה הראשונה שצריך להכיר היא gh run list. כשאני מריץ את הפקודה מתיקיה עם ריפוזיטורי, היא מדפיסה למסך את כל ההרצות של ה Actions מהריפוזיטורי. בעזרת המתג --json אני יכול לבקש רק פרטים מסוימים על כל הרצה ובעזרת -L אני מגביל את מספר התוצאות. ככה זה נראה עם ריפו שלי בהגבלה ל-3 הרצות אחרונות:

$ PAGER= gh run list --json name,databaseId,startedAt -L 3

[
  {
    "databaseId": 4289773851,
    "name": "publish-daily-post-to-telegram",
    "startedAt": "2023-02-28T05:11:50Z"
  },
  {
    "databaseId": 4279130715,
    "name": "publish-daily-post-to-telegram",
    "startedAt": "2023-02-27T05:11:47Z"
  },
  {
    "databaseId": 4273470712,
    "name": "publish-daily-post-to-telegram",
    "startedAt": "2023-02-26T05:10:54Z"
  }
]

הפקודה השניה שצריך להכיר היא gh run view שמקבלת מספר ריצה ומדפיסה פרטים עליה. המתג --log יגרום ל view להציג את הלוג המלא של ההרצה. ככה זה נראה אצלי עם ההרצה העדכנית ביותר:

$ PAGER= gh run view --log 4289773851 | head

publish Set up job      2023-02-28T05:12:00.4187561Z Current runner version: '2.301.1'
publish Set up job      2023-02-28T05:12:00.4215234Z ##[group]Operating System
publish Set up job      2023-02-28T05:12:00.4215881Z Ubuntu
publish Set up job      2023-02-28T05:12:00.4216132Z 22.04.2
publish Set up job      2023-02-28T05:12:00.4216414Z LTS
publish Set up job      2023-02-28T05:12:00.4216728Z ##[endgroup]
publish Set up job      2023-02-28T05:12:00.4217052Z ##[group]Runner Image
publish Set up job      2023-02-28T05:12:00.4217433Z Image: ubuntu-22.04
publish Set up job      2023-02-28T05:12:00.4217721Z Version: 20230219.1
publish Set up job      2023-02-28T05:12:00.4218252Z Included Software: https://github.com/actions/runner-images/blob/ubuntu22/20230219.1/images/linux/Ubuntu2204-Readme.md

וזה כמובן ממשיך לכל הלוג.

שילוב של שתי הפקודות יאפשר לי בשורה אחת לקבל את הלוג המלא של ההרצה האחרונה של Action בריפו:

$ PAGER= gh run view --log $(gh run list --json databaseId -q '.[].databaseId' -L 1)

נ.ב. המתג -q מקבל שאילתה בפורמט של jq. בזכותו פלט הפקודה היה רק מזהה הריצה במקום ה JSON המלא.

מה הסיפור עם הלוכסנים אחרי כתובת IP?

28/02/2023

נתקלתם פעם בכתובת 10.10.64.0/27? ומה לגבי 192.168.0.0/24? כשאנחנו מדברים על אוסף כתובות IP ברשת מסוימת הרבה פעמים נבחר לכתוב אותן בתור כתובת ואחריה לוכסן ואז מספר, בכתיב שנקרא CIDR Block. האותיות CIDR הן ראשי תיבות של המילים Classless Inter-Domain Routing. השיטה עצמה הוצגה כבר ב 1993 במטרה לחסוך בכתובת IP ולספק גמישות לארגונים שרצו לחלק את הרשת הפנימית שלהם למקטעים.

חלוקת רשת למקטעים חוסכת בכתובות IP כי לא כל מכונה ברשת הפנימית צריכה כתובת IP ציבורית. חישבו על הרשת הפנימית שלכם בבית - המחשבים יכולים לקבל כתובת IP כמו 192.168.0.124, ובעזרתה המכונות ברשת הפנימית מתקשרות אחת עם השניה, אבל רק כתובת ה IP החיצונית של הראוטר תהיה נגישה מהאינטרנט. בניית רשת פנימית חסכה לספק האינטרנט שלכם כתובות IP, כי עכשיו הם יכולים לתת לכם רק כתובת IP אחת בשביל הראוטר ולא צריכים להקצות כתובת לכל מכונה ברשת הביתית.

כתיב CIDR הוא פשוט דרך לייצג אוסף של כתובות IP עוקבות. כשהראוטר שלכם רואה הודעה שנשלחת בין שתי מכונות בתוך אותו בלוק שהוא מנהל, הוא לא צריך לשלוח את ההודעה החוצה ל ISP, ויכול מיד להעביר אותה למכונת היעד.

לכן כשאני כותב 192.168.0.0/30 אני בעצם כותב את 4 כתובות ה IP:

192.168.0.0
192.168.0.1
192.168.0.2
192.168.0.3

ובאופן כללי כתיב של כתובת IP שאחריה לוכסן ואז מספר מפורש בתור בלוק של כתובות IP צמודות.

איך יודעים איזה כתובות IP מתאימות ל CIDR Block מסוים? טוב ששאלתם. קודם כל יש תוכנות שפורסות רשימות כאלה, לדוגמה אם יש לכם nmap מותקן תוכלו לכתוב:

$ nmap -sL -n 192.168.0.0/30| awk '/for/{print $NF}'

ולקבל בדיוק את הרשימה (ה awk בסוף בא לסנן כמה שורות לא חשובות ש nmap מדפיס). ואפשר גם לחשב את הרשימה יחסית בקלות כשמבינים מה הכתיב מייצג. כל כתובת IP ניתנת לייצוג בגירסה בינארית באמצעות המרה לבינארי של כל אחד מהמספרים שמרכיב אותה. לדוגמה הכתובת 172.16.21.9 מיוצגת בבינארית על ידי המספר:

10101100.00010000.00010101.00001001

בכתיב CIDR המספר שאחרי הלוכסן מייצג כמה ביטים משמשים לייצוג הרשת, ובעצם יישארו קבועים בכל כתובת IP שנכתוב. המספר הזה נע בין 0, המייצג בלוק של כל כתובות ה IP האפשריות, ל 32 המייצג בלוק שמורכב מכתובת IP בודדת. בשביל שיהיה נוח לכתוב, כתובת הבסיס של בלוק CIDR מגדירה את הביטים של ה Host להיות מאופסים.

אז אם נשאר עם הכתובת 172.16.21.9 אני יכול לדבר על הבלוק 172.16.21.9/31 בו נמצאות רק שתי הכתובת:

172.16.21.8
172.16.21.9

או בכתיב בינארי:

10101100.00010000.00010101.00001000
10101100.00010000.00010101.00001001

ושימו לב איך ה 31 ביטים הראשונים זהים ורק הביט האחרון משתנה מ 0 ל 1. ככל שאוריד את המספר שאחרי הלוכסן (מספר הביטים של הרשת), כך יהיו לי יותר כתובות IP שונות בבלוק. הבלוק 172.16.0.0/24 כבר מכיל 256 כתובות - כל הכתובות בהן שלושת המספרים הראשונים הם 172.16.0. הבלוק 172.16.0.0/28 מכיל רק 16 כתובות, הראשונה היא 172.16.0.0 והאחרונה היא 172.16.0.15. בשביל להגיע לבלוק הבא של 16 כתובות אני מגדיל את המספר האחרון בכתובת, כלומר הבלוק 172.16.0.16/28 מכיל גם הוא 16 כתובות אבל הפעם הכתובת הראשונה היא 172.16.0.16 והאחרונה היא 172.16.0.31.

באגים בלתי נראים

27/02/2023

כלי פיתוח טובים הם קודם כל כלים שיודעים להצביע (ברמת דיוק גבוהה) על קוד שנראה כמו באג. וכן, כלי פיתוח קשורים לשפה ועובדים יחד איתה. בדוגמה קלאסית מ JavaScript, הקוד הזה שגוי אבל ל eslint לא אכפת:

function countUntil(n) {
  let i=0;
  while(i < n)
    console.log(i);
    i++;
}

countUntil(10);

קל לראות שהשורה השניה ב"בלוק" ה while היא בעצם בכלל לא בבלוק ה while, ולכן הלולאה תרוץ עד אינסוף. בשביל לתקן אני לא צריך לעבור לפייתון, מספיק להוסיף ל eslint את הכלל indent כדי שהוא יצעק שהשורה i++ לא במקום.

אחת העבודות החשובות שלנו כמתכנתים היא למצוא את הכלים הנכונים - לא הכלים שיכתבו במקומנו את הקוד, אלא קודם כל הכלים שיצביעו על שגיאות שאולי לעין קל לפספס.

המסמך הכי חשוב שאתם לא כותבים

26/02/2023

שידרוגי תוכנה זה קשה וככל שהמערכת בשלה ומורכבת יותר זה רק נהיה יותר מסובך. בעיה ראשונה ומוכרת היא שגירסאות חדשות של תלויות יוצאות כל הזמן, לפעמים באמת מתקנות בעיות אבל הרבה פעמים גם שוברות תאימות API. שידרוג תלויות יכול לשבור הרבה דברים במערכת.

בעיה שניה ואפילו יותר מטרידה היא שכמות המערכות שיש להן תלויות גדלה, וקורה שאנחנו מאבדים קשר עם כל התלויות שצריך לשדרג ואיך לשדרג כל דבר.

במערכת Full Stack טיפוסית יהיו לנו את מערכת ההפעלה של השרת, גירסאות של כל הספריות המובנות במערכת ההפעלה, בסיס הנתונים, מעל זה תוכנות נלוות שהתקנו בשביל האפליקציה, כשהאפליקציה רצה בתוך Docker Container אז קבצי ה Dockerfile מתיחסים לתוכנות בגירסאות מסוימות (וגם אותן צריך לשדרג), וברמת האפליקציה ה requirements.txt, Gemfile או package.json צריכים טיפול ושדרוג.

לכן במקום להגיד "נשדרג כשיהיה זמן" או לשדרג כשמשהו נשבר, מומלץ לקבע את השידרוגים במסמך אחד שאומר הכל. טבלת אקסל תעבוד פה טוב, אבל אפשר גם קובץ טקסט פשוט. העמודות יהיו "מה", "איך לשדרג", "מתי שדרגנו פעם אחרונה", "תאריך שידרוג הבא", לדוגמה:

  1. בסיס הנתונים, קישור למדריך שמסביר איך לשדרג את בסיס הנתונים, שודרג לאחרונה ב XXX, ישודרג פעם הבאה ב YYY.

  2. קובץ ה Dockerfile של השרת, קישור למסמך שמסביר איך לשדרג, שודרג לאחרונה ב XXX ישודרג פעם הבאה ב YYY.

  3. תלויות צד-לקוח של האפליקציה הראשית, קישור למסמך שמסביר איך לשדרג, שודרג לאחרונה ב XXX, ישודרג פעם הבאה ב YYY.

טבלת אקסל מסודרת עם לינקים שתעודכן בכל שידרוג יכולה להפוך את השידרוגים לחוויה הרבה פחות מלחיצה ובלי לשכוח אף חלק במערכת.

שלוש סיבות לבחור ב Tauri לבניית אפליקציית ה Desktop הבאה שלכם

25/02/2023

טאורי הוא כלי שמאפשר לכתוב אפליקציית Desktop בטכנולוגיות ווב. ואם המשפט הזה מזכיר לכם כלי אחר שמתחרז עם "עפרון", הנה שלוש סיבות שבגללן אני מתכנן לבחור בטאורי לפרויקט ה Desktop הבא שלי:

  1. גודל הקובץ - יישום אלקטרון פשוט יתחיל ב 50 מגה ויכול להגיע בקלות לכמה מאות. טאורי לא כולל את כרומיום ומשתמש ב Web View שכבר קיים במערכת ההפעלה, לכן יישומים פשוטים לוקחים בסך הכל 3-4 מגה.

  2. ראסט - טאורי משתמש ב Rust בשביל לתקשר עם מערכת ההפעלה ובנוי על תהליך Rust שמפעיל את ה Web View של מערכת ההפעלה ושם פותח את קבצי ה HTML/CSS/JS שלכם.

  3. אבטחת מידע - לבעיות XSS באפליקציות אלקטרון היו השלכות הרסניות, כיוון שקוד ה JavaScript קיבל גישה מלאה למחשב (דרך APIs של אלקטרון). טאורי לוקח גישה יותר מאובטחת ונותן לקוד ה JavaScript גישה רק ל APIs שבחרנו בצורה מפורשת.

רוצים לראות איך האפליקציה שלכם תעבוד בתור Desktop App? המדריך כאן מסביר איך להוסיף את טאורי לכל Web Application כדי להפוך אותו לאפליקציית Desktop, ולדעתי הוא מקום טוב להתחיל לשחק עם הכלי: https://tauri.app/v1/guides/getting-started/setup/integrate

שתי מוטיבציות

24/02/2023

יש מוטיבציה שנוצרת מפחד- לדוגמה הרבה אנשים אחרי ראיון עבודה יוצאים עם הרגשה של "אני לא יודע כלום", ואז כמו מקבלים זריקת מרץ ללמוד כמה שיותר כדי לבוא מוכנים לראיון הבא.

הבעיה שזריקת המרץ הזאת לא מחזיקה מעמד הרבה זמן. מוטיבציה שנובעת מפחד נגמרת כשהפחד נגמר, ופחד הוא רגש זמני. (כשהפחד הופך לכרוני הוא כבר נקרא חרדה, וחרדה לא מניעה אף אחד לפעולה).

הסוג השני של מוטיבציה נוצר מהשראה (Inspiration)- אנשים כמונו עושים דברים בצורה כזאת. הבחירה של מתכנתים רבים ללמוד טכנולוגיה חדשה כי "זה מעניין" או "זה כלי שחשוב להכיר" היא סוג כזה של מוטיבציה. אף אחד לא יפטר אותך אם לא תלמד Web Assembly, אבל הרבה מתכנתי ווב כבר רואים את הפוטנציאל ומתחילים ללמוד את הטכנולוגיה.

שני הסוגים חשובים אבל רק את הסוג השני אנחנו צריכים לחפש באופן אקטיבי. כשאתם רוצים להתקדם, שווה להתאמץ ולמצוא השראה טוב יותר.

עידכון אפליקציית ריאקט אחרי קבלת אירוע מהשרת

23/02/2023

מערכות ווב מסורתיות היו צריכות להתמודד רק עם אירועים שמגיעים מהמשתמש שעכשיו גולש באתר. בעולם המודרני והמסוכנרן שלנו, מערכות ווב כבר צריכות להתמודד גם עם פעולות משתמש וגם עם אירועים שמגיעים מרחוק, הרבה פעמים מהשרת, ולסנכרן בין השניים. בואו נראה למה זה קשה ודרך אחת להתמודד עם האתגר באפליקציית ריאקט.

המשך קריאה

חדש ב Ruby: אוביקט מידע

22/02/2023

רובי 3.2 יצאה לא מזמן וכולם מדברים על התמיכה ב Web Assembly שהיא הוסיפה - וזה באמת פיצ'ר מדליק שפותח הרבה אפשרויות, אבל לא בטוח איך הוא הולך לעזור לי בכתיבת קוד ביום יום.

פיצ'ר אחר שזכה לקצת פחות כותרות נראה לי הרבה יותר שימושי ועליו הפוסט היום והוא נקרא פשוט Data. כן דומה מאוד ל Data Class של פייתון.

איך זה עובד? אז Data מספק דרך להגדיר אוביקט מידע שלא ניתן לשינוי, אפשר להשוות בין אוביקטי מידע כאלה והוא שומר על הייצוג שלו בכל מקום. זה קצת דומה ל Struct אבל עם פחות אפשרויות וממשק קל יותר.

לדוגמה אם אני רוצה לייצג במערכת משתמש שיש לו מזהה מספרי וכתובת אימייל כדי להעביר בין כמה מקומות במערכת אני יכול ברובי 3.2 לכתוב משהו כזה:

User = Data.define(:name, :email)

u1 = User.new(name: 'dave', email: 'dave@e.com')
u2 = User.new(name: 'john', email: 'john@e.com')

puts u1.email

puts "u1 == u2 ? #{u1 == u2}"

עד לפה הכל קל ודומה ל Struct, אבל יש שני הבדלים מרכזיים:

  1. אוביקטי Data לא מגדירים את to_a לכן אי אפשר להמיר Data Object למערך של כל הערכים שבו (כמובן שאין בעיה להפעיל to_h כדי לקבל Hash)

  2. אוביקטי Data לא ניתנים לשינוי, וכך שומרים עלינו שלא נעשה טעויות מוזרות - למשל נקבל אוביקט Data באיזו פונקציה, נשנה אותו וכך נגרום לנזק במקום אחר בקוד.

סך הכל שינוי קטן שיכול להפוך קוד רובי לקצת יותר נעים.

נכשלתי במבחן פישינג (או: למה לינקים במייל זה כל כך מסובך)

21/02/2023

מייקרוסופט החליטו לעשות לי מבחן פישינג ושלחו לי מייל שמספר לי שחבר שיתף איתי הערות על פרויקט. הכרתי את שם החבר אבל היה נראה לי מוזר שהוא משתף הערות על פרויקט כי אנחנו לא עובדים על אף פרויקט יחד עכשיו, לכן לחצתי על הקישור כדי לראות על מה מדובר והגעתי לדף שמסביר שזה היה רק תרגיל פישינג ובעצם אף אחד לא שיתף איתי כלום.

לקח כמה רגעים להתאושש מהאכזבה, אחריהם ניסיתי לשחזר איך זה קרה ולמה לינקים במייל זה כל כך מסובך. נתחיל באמת הפשוטה - לינקים במייל באמת יכולים להיות מסוכנים. אלה הדברים הרעים המרכזיים שיכולים לקרות כשאנחנו לוחצים על לינק בלי לדעת לאן הוא מוביל:

  1. יש אנשים שפשוט רוצים לדעת שמישהו קורא את המיילים שלהם, כדי שיוכלו לשלוח עוד ועוד מיילים. לחיצה על לינק מאשרת לאותם אנשים שכתובת המייל שלכם אמיתית ואתם אנשים אמיתיים שקוראים דואר בכתובת הזאת.

  2. יש אתרים עם באגים, במיוחד באגים כמו XSS ו CSRF. באג דמיוני כזה יכול לגרום לכם לעשות בלי כוונה פעולה באתר שאתם כבר מחוברים אליו מטאב אחר. לדוגמה נדמיין שאני מחובר לפייפאל שלי מטאב אחר, ובפייפאל יש כפתור ששולח כסף לחבר, אז בגלל באג באתר תוקף יכול לשלוח אליי לינק לדף מיוחד בפייפאל שכשאני אכנס אליו אתר פייפאל יחשוב שלחצתי על כפתור לשליחת כסף לאותו תוקף.

  3. יש באגים בדפדפנים. ברור שאם חברת גוגל או מייקרוסופט ישמעו על באג בדפדפן שלהן הן יפיצו מיד תיקון, אבל הרבה פעמים אתם לא משדרגים גירסה בדפדפן מיד כשהוא מבקש לשדרג, ועוד יותר הרבה פעמים אנשים שומרים בסוד את הבאגים ולא מספרים עליהם ליצרניות הדפדפנים. באג בדפדפן יכול לגרום לדפדפן שלכם להריץ תוכנית זדונית רק בגלל שלחצתם על לינק.

בהתחשב בכל הסכנות האלה אפשר היה לדמיין שנהיה יותר זהירים בלחיצות על לינקים, אבל יש בעיה - לחיצה על לינקים עדיין מהווה חלק מרכזי בעבודה היום יומית שלנו ובאינטרקציה עם המחשב. כמה דוגמאות מתיבת המייל שלי:

  1. הזמנה לשיחות עבודה בזום או בטימס מגיעות למייל, ונכנסים אליהן באמצעות לחיצה על הלינק.

  2. עדכונים על PR בגיטהאב מגיעים למייל עם לינק ל PR.

  3. ניוזלטרים שנשלחים למייל מגיעים מלאים בלינקים, ואנחנו רגילים ללחוץ על לינקים למאמרים שמעניינים אותנו.

  4. אחרי קניה באמזון תקבלו מייל עם פרטי הזמנה ואפשרות ללחוץ על לינק כדי להיכנס לדף ההזמנה שהרגע ביצעתם.

  5. חברות רבות ישלחו חשבונית בתור קובץ מצורף, אבל חברות רבות אחרות ישלחו אותה בתור לינק לקובץ PDF ברשת.

נסו יום אחד לרשום את כל הלינקים שאתם לוחצים עליהם מהמייל ותגלו שזה לא משהו שאפשר לעצור. בנוסף אנחנו יודעים שכתובת מייל שולח היא לא דרך אמינה לאמת את מקור המייל, ומיילים זדוניים יכולים להגיע גם מתיבות דואר של אנשים שאנחנו סומכים עליהם. בנוסף אפילו אם במייל כתוב בפירוש הלינק, הלחיצה עליו יכולה להוביל למקום אחר מזה שכתוב (כי הטקסט של הלינק יכול להיות שונה מכתובת היעד) ולא נראה את זה עד שנלחץ.

אפילו אם ננסה להעתיק את הלינק הצידה ולסרוק אותו בעין לפני שניכנס לאתר זה לא מספיק טוב, כי לינק לאתר זדוני יכול להיראות בדיוק כמו לינק לאתר רגיל, או שהלינק יכיל שגיאת כתיב קטנה שבקריאה רגילה לא נשים לב אליה. וכמובן לינקים באימייל הרבה פעמים שולחים אתכם דרך "לינק מתווך" של חברת הדיוור כדי שיוכלו לדעת מי לחץ על איזה לינק.

פיתרון? אני לא רואה באופק. ברמה הפרקטית העצות הנפוצות עדיין נכונות - לא ללחוץ על לינקים מזרים, לא לפתוח מייל מאנשים שלא מכירים וכו', אבל ברמת האבטחה זה רחוק מ 100%. יש גם לא מעט תוכנות שתפקידן לזהות פישינג או לינקים חשודים, אבל קשה לראות את רובנו עוברים להתקין ולשלם על פיתרונות כאלה. פיתרון ברמת ספק המייל יכול לעזור אבל גם זה לא מושלם כי לינקים מגיעים גם דרך הווטסאפ או טלגרם.