Zelyanoth commited on
Commit
aa1cf8a
·
1 Parent(s): bacdae8
Post_management.py CHANGED
@@ -6,13 +6,22 @@ days_of_week = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturda
6
  Social_network_menu = ["Linkedin"]
7
 
8
 
 
 
 
 
 
 
 
 
9
  with tgb.Page(class_name="bodyp") as Post_manag :
10
  with tgb.part(class_name="source_body") :
11
  tgb.text("Post Management", class_name="Title_Page")
12
  with tgb.part(class_name="layout_top") :
13
  tgb.text("Linking Account", class_name="header-burgundy")
14
  with tgb.layout(columns="1", class_name="table_t") : # Single column for full width
15
- with tgb.layout(columns="1 1 1", gap="16px") : # Add gap for harmony
 
16
  tgb.input("{Linked_account_name}", label="Account name", change_delay=-1, action_on_blur=True, width="100%")
17
  tgb.selector("{Linked_social_network}", lov=Social_network_menu, dropdown=True, label="Select platform", width="100%")
18
  tgb.button(label="Add account", on_action=authen, class_name="btn-burgundy", width="100%")
@@ -23,22 +32,22 @@ with tgb.Page(class_name="bodyp") as Post_manag :
23
  tgb.input("{generated_post}", multiline=True, class_name="burgundy-border", width="100%")
24
  tgb.button(label="Publish", on_action=post_publishing, width="100%")
25
  with tgb.part(class_name="table_s") :
26
- tgb.text("Post Scheduling", class_name="header-burgundy") # Ajoute un peu d'espace en bas du titre
27
  # Texte explicatif juste en dessous du titre, avec exemple
28
  tgb.text(
29
  "⏰ Pour garantir la bonne planification, l'heure choisie doit être au moins 10 minutes après l'heure actuelle. "
30
  "Par exemple, si il est 14h00, choisissez au minimum 14h10. "
31
  "Sinon, la tâche sera programmée pour la prochaine occurrence du créneau choisi.",
32
  class_name="info-message"
33
-
34
  )
35
- with tgb.layout(columns="1", class_name="table_t") :
36
- with tgb.layout(columns="1 1 1 1", gap="16px") :
 
37
  tgb.input("{time_value_hour}", label="Hour", change_delay=-1, action_on_blur=True, width="100%")
38
  tgb.input("{time_value_minute}", label="Minute", change_delay=-1, action_on_blur=True, width="100%")
39
  tgb.selector("{day_value}", lov=days_of_week, multiple=True, dropdown=True, show_select_all=True, width="100%")
40
  tgb.button(label="Add", on_action=add_scheduling, width="100%")
41
- tgb.table("{data_schedule}", editable=True, on_add=False, on_edit=False, on_delete=delete_schedule, class_name="table_t", width="100%")
42
 
43
  # Date complète
44
  # tgb.date("{day_value}", format="EEEE, MMM dd, yyyy", label="Full Date with Day")
 
6
  Social_network_menu = ["Linkedin"]
7
 
8
 
9
+ from taipy import Gui
10
+ import taipy.gui.builder as tgb
11
+ from functi import post_generation,post_publishing,authen,add_scheduling,delete_account,delete_schedule
12
+
13
+ days_of_week = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
14
+ Social_network_menu = ["Linkedin"]
15
+
16
+
17
  with tgb.Page(class_name="bodyp") as Post_manag :
18
  with tgb.part(class_name="source_body") :
19
  tgb.text("Post Management", class_name="Title_Page")
20
  with tgb.part(class_name="layout_top") :
21
  tgb.text("Linking Account", class_name="header-burgundy")
22
  with tgb.layout(columns="1", class_name="table_t") : # Single column for full width
23
+ # Mobile responsive: 3 columns on desktop, 1 column on mobile
24
+ with tgb.layout(columns="1 1 1", columns_mobile="1", gap="16px") :
25
  tgb.input("{Linked_account_name}", label="Account name", change_delay=-1, action_on_blur=True, width="100%")
26
  tgb.selector("{Linked_social_network}", lov=Social_network_menu, dropdown=True, label="Select platform", width="100%")
27
  tgb.button(label="Add account", on_action=authen, class_name="btn-burgundy", width="100%")
 
32
  tgb.input("{generated_post}", multiline=True, class_name="burgundy-border", width="100%")
33
  tgb.button(label="Publish", on_action=post_publishing, width="100%")
34
  with tgb.part(class_name="table_s") :
35
+ tgb.text("Post Scheduling", class_name="header-burgundy")
36
  # Texte explicatif juste en dessous du titre, avec exemple
37
  tgb.text(
38
  "⏰ Pour garantir la bonne planification, l'heure choisie doit être au moins 10 minutes après l'heure actuelle. "
39
  "Par exemple, si il est 14h00, choisissez au minimum 14h10. "
40
  "Sinon, la tâche sera programmée pour la prochaine occurrence du créneau choisi.",
41
  class_name="info-message"
 
42
  )
43
+ with tgb.layout(columns="1", class_name="table_t ") :
44
+ # Mobile responsive: 4 columns on desktop, 1 column on mobile
45
+ with tgb.layout(columns="1 1 1 1", columns_mobile="1", gap="16px") :
46
  tgb.input("{time_value_hour}", label="Hour", change_delay=-1, action_on_blur=True, width="100%")
47
  tgb.input("{time_value_minute}", label="Minute", change_delay=-1, action_on_blur=True, width="100%")
48
  tgb.selector("{day_value}", lov=days_of_week, multiple=True, dropdown=True, show_select_all=True, width="100%")
49
  tgb.button(label="Add", on_action=add_scheduling, width="100%")
50
+ tgb.table("{data_schedule}", editable=True, on_add=False, on_edit=False, on_delete=delete_schedule, class_name="table_t", width="100%", columns="social_network;schedule_time")
51
 
52
  # Date complète
53
  # tgb.date("{day_value}", format="EEEE, MMM dd, yyyy", label="Full Date with Day")
__pycache__/Post_management.cpython-311.pyc CHANGED
Binary files a/__pycache__/Post_management.cpython-311.pyc and b/__pycache__/Post_management.cpython-311.pyc differ
 
__pycache__/functi.cpython-311.pyc CHANGED
Binary files a/__pycache__/functi.cpython-311.pyc and b/__pycache__/functi.cpython-311.pyc differ
 
__pycache__/line_db.cpython-311.pyc CHANGED
Binary files a/__pycache__/line_db.cpython-311.pyc and b/__pycache__/line_db.cpython-311.pyc differ
 
__pycache__/timing_lin.cpython-311.pyc CHANGED
Binary files a/__pycache__/timing_lin.cpython-311.pyc and b/__pycache__/timing_lin.cpython-311.pyc differ
 
app.css CHANGED
@@ -134,4 +134,58 @@ html, body {
134
  font-size: 1.3em;
135
  padding: 15px 20px;
136
  }
137
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  font-size: 1.3em;
135
  padding: 15px 20px;
136
  }
137
+ }
138
+
139
+ @media (max-width: 768px) {
140
+ .source_body {
141
+ margin: 1em;
142
+ padding: 0.5em;
143
+ }
144
+
145
+ .layout_top {
146
+ padding-top: 2em;
147
+ }
148
+
149
+ .table_s, .table_t {
150
+ padding-top: 1em;
151
+ }
152
+
153
+ /* Make inputs and buttons more touch-friendly on mobile */
154
+ input, button, select {
155
+ min-height: 44px; /* Apple's recommended touch target size */
156
+ font-size: 16px; /* Prevents zoom on iOS */
157
+ }
158
+
159
+ /* Improve table readability on mobile */
160
+ .taipy-table {
161
+ font-size: 14px;
162
+ }
163
+
164
+ /* Better text wrapping for info messages */
165
+ .info-message {
166
+ font-size: 14px;
167
+ line-height: 1.4;
168
+ margin-bottom: 1em;
169
+ }
170
+
171
+ /* Ensure burgundy borders are visible on mobile */
172
+ .burgundy-border {
173
+ border-width: 1px;
174
+ margin-bottom: 1em;
175
+ }
176
+ }
177
+
178
+ /* Improve button styling for mobile */
179
+ @media (max-width: 480px) {
180
+ .source_body {
181
+ margin: 0.5em;
182
+ }
183
+
184
+ .Title_Page {
185
+ font-size: 1.5em;
186
+ }
187
+
188
+ .header-burgundy {
189
+ font-size: 1.1em;
190
+ }
191
+ }
app.py CHANGED
@@ -185,7 +185,7 @@ if __name__ == "__main__":
185
  gui = Gui(pages=pages)
186
  gui.run(
187
  debug=True,
188
- port=7860,host = "0.0.0.0",
189
  stylekit=stylekit,
190
  title="Lin",
191
  dark_mode=False,
 
185
  gui = Gui(pages=pages)
186
  gui.run(
187
  debug=True,
188
+ # port=7860,host = "0.0.0.0",
189
  stylekit=stylekit,
190
  title="Lin",
191
  dark_mode=False,
functi.py CHANGED
@@ -86,7 +86,10 @@ def post_generation_for_robot(id,social,idd) :
86
  code=id,
87
  api_name="/poster_linkedin"
88
  )
89
- db_manager.add_post(social,generated_post,idd)
 
 
 
90
  except Exception as e:
91
  print("Erreur dans gen():", e, flush=True)
92
 
@@ -101,8 +104,7 @@ def post_publishing_for_robot(id_social,id_user,idd,ss) :
101
  token_value = first["token"]
102
  sub_value = first["sub"]
103
  post = dd["Text_content"].iloc[0]
104
-
105
- print("⏳ Tâche planifiée pour gfjfxd",flush = True)
106
 
107
  url = "https://api.linkedin.com/v2/ugcPosts"
108
  headers = {
@@ -110,26 +112,82 @@ def post_publishing_for_robot(id_social,id_user,idd,ss) :
110
  "X-Restli-Protocol-Version": "2.0.0",
111
  "Content-Type": "application/json"
112
  }
113
- body = {
114
- "author": f"urn:li:person:{sub_value}",
115
- "lifecycleState": "PUBLISHED",
116
- "specificContent": {
117
- "com.linkedin.ugc.ShareContent": {
118
- "shareCommentary": {
119
- "text": post
120
- },
121
- "shareMediaCategory": "NONE"
122
- }
123
- },
124
- "visibility": {
125
- "com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC"
126
  }
127
  }
128
- resp = requests.post(url, headers=headers, json=body)
129
- db_manager.update_post(id_social,idd)
130
- print([resp.status_code, resp.text],flush = True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  except Exception as e:
132
- print("Erreur dans post():", e, flush=True)
 
 
 
133
 
134
  def planifier_ligne(id_schedule, id_social, user_id, schedule_time_str, ss, adjusted_time):
135
  # Parse schedule_time_str and adjusted_time
@@ -208,7 +266,7 @@ def add_scheduling(state):
208
  jour, horaire = timesche.split()
209
  horaire = horaire.replace(';', ':')
210
  h, m = map(int, horaire.split(':'))
211
- m -= 7 # 7 minutes before for generation
212
  final_time = f"{jour} {h}:{m:02d}"
213
 
214
  # Add to database
@@ -230,7 +288,7 @@ def add_scheduling(state):
230
  jour, horaire = timesche.split()
231
  horaire = horaire.replace(';', ':')
232
  h, m = map(int, horaire.split(':'))
233
- m -= 7 # 7 minutes before for generation
234
  final_time = f"{jour} {h}:{m:02d}"
235
 
236
  # Add to database
@@ -293,6 +351,7 @@ def post_generation(state) :
293
  code=state.user_inf.user.id,
294
  api_name="/poster_linkedin"
295
  )
 
296
 
297
  def authen(state) :
298
  if state.Linked_social_network == "Linkedin" :
 
86
  code=id,
87
  api_name="/poster_linkedin"
88
  )
89
+ generated_post = eval(generated_post)
90
+ print(generated_post,flush = True)
91
+ print(generated_post[1],flush = True)
92
+ db_manager.add_post(social,generated_post[0],idd)
93
  except Exception as e:
94
  print("Erreur dans gen():", e, flush=True)
95
 
 
104
  token_value = first["token"]
105
  sub_value = first["sub"]
106
  post = dd["Text_content"].iloc[0]
107
+ img = dd["image_content_url"].iloc[0]
 
108
 
109
  url = "https://api.linkedin.com/v2/ugcPosts"
110
  headers = {
 
112
  "X-Restli-Protocol-Version": "2.0.0",
113
  "Content-Type": "application/json"
114
  }
115
+
116
+
117
+ if img is not None:
118
+ register_body = {
119
+ "registerUploadRequest": {
120
+ "recipes": ["urn:li:digitalmediaRecipe:feedshare-image"],
121
+ "owner": sub_value,
122
+ "serviceRelationships": [{
123
+ "relationshipType": "OWNER",
124
+ "identifier": "urn:li:userGeneratedContent"
125
+ }]
 
 
126
  }
127
  }
128
+ r = requests.post("https://api.linkedin.com/v2/assets?action=registerUpload",
129
+ headers=headers, json=register_body)
130
+ datar = r.json()["value"]
131
+ upload_url = datar["uploadMechanism"]["com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest"]["uploadUrl"]
132
+ asset_urn = datar["asset"]
133
+
134
+ upload_headers = {
135
+ "Authorization": f"Bearer {token_value}",
136
+ "X-Restli-Protocol-Version": "2.0.0",
137
+ "Content-Type": "application/octet-stream"
138
+ }
139
+ response = requests.put(upload_url, headers=upload_headers, data=img)
140
+ if response.status_code not in (200,201):
141
+ print("Erreur upload:", response.status_code, response.text)
142
+
143
+ post_body = {
144
+ "author": sub_value,
145
+ "lifecycleState": "PUBLISHED",
146
+ "specificContent": {
147
+ "com.linkedin.ugc.ShareContent": {
148
+ "shareCommentary": {"text": post},
149
+ "shareMediaCategory": "IMAGE",
150
+ "media": [{
151
+ "status": "READY",
152
+ "media": asset_urn,
153
+ "description": {"text": "Une belle image"},
154
+ "title": {"text": "Titre image"}
155
+ }]
156
+ }
157
+ },
158
+ "visibility": {"com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC"}
159
+ }
160
+ resp = requests.post("https://api.linkedin.com/v2/ugcPosts",
161
+ headers=headers, json=post_body)
162
+ print(resp.status_code, resp.text)
163
+ else:
164
+
165
+ print("⏳ Tâche planifiée pour gfjfxd",flush = True)
166
+
167
+
168
+ body = {
169
+ "author": f"urn:li:person:{sub_value}",
170
+ "lifecycleState": "PUBLISHED",
171
+ "specificContent": {
172
+ "com.linkedin.ugc.ShareContent": {
173
+ "shareCommentary": {
174
+ "text": post
175
+ },
176
+ "shareMediaCategory": "NONE"
177
+ }
178
+ },
179
+ "visibility": {
180
+ "com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC"
181
+ }
182
+ }
183
+ resp = requests.post(url, headers=headers, json=body)
184
+ db_manager.update_post(id_social,idd)
185
+ print([resp.status_code, resp.text],flush = True)
186
  except Exception as e:
187
+ print("Erreur dans post():", e, flush=True)
188
+
189
+
190
+
191
 
192
  def planifier_ligne(id_schedule, id_social, user_id, schedule_time_str, ss, adjusted_time):
193
  # Parse schedule_time_str and adjusted_time
 
266
  jour, horaire = timesche.split()
267
  horaire = horaire.replace(';', ':')
268
  h, m = map(int, horaire.split(':'))
269
+ m -= 5 # 7 minutes before for generation
270
  final_time = f"{jour} {h}:{m:02d}"
271
 
272
  # Add to database
 
288
  jour, horaire = timesche.split()
289
  horaire = horaire.replace(';', ':')
290
  h, m = map(int, horaire.split(':'))
291
+ m -= 5 # 7 minutes before for generation
292
  final_time = f"{jour} {h}:{m:02d}"
293
 
294
  # Add to database
 
351
  code=state.user_inf.user.id,
352
  api_name="/poster_linkedin"
353
  )
354
+ state.generated_post = state.generated_post[0]
355
 
356
  def authen(state) :
357
  if state.Linked_social_network == "Linkedin" :
line_db.py CHANGED
@@ -2,6 +2,8 @@ import os
2
  from supabase import create_client, Client
3
  from pandas import json_normalize
4
  import pandas
 
 
5
 
6
  class DatabaseManager:
7
 
@@ -99,10 +101,14 @@ class DatabaseManager:
99
  "sub" : data["sub"],"given_name" : data["given_name"],"family_name" : data["family_name"],"picture" : data["picture"] })
100
  .execute()
101
  )
102
- def add_post(self,id_social,content,ids,tg : bool =False) :
 
 
 
 
103
  response = (
104
  self.supabase.table("Post_content")
105
- .insert({"id_social": id_social,"Text_content" :content ,"is_published" : tg,"sched" : ids })
106
  .execute())
107
 
108
  def update_post(self,ids,idd):
 
2
  from supabase import create_client, Client
3
  from pandas import json_normalize
4
  import pandas
5
+ import io
6
+ from PIL import Image
7
 
8
  class DatabaseManager:
9
 
 
101
  "sub" : data["sub"],"given_name" : data["given_name"],"family_name" : data["family_name"],"picture" : data["picture"] })
102
  .execute()
103
  )
104
+ def add_post(self,id_social,content,ids,img: Image.Image = None,tg : bool =False) :
105
+ if img :
106
+ buffer = io.BytesIO()
107
+ img.save(buffer, format="JPEG")
108
+ img = buffer.getvalue()
109
  response = (
110
  self.supabase.table("Post_content")
111
+ .insert({"id_social": id_social,"Text_content" :content ,"is_published" : tg,"sched" : ids,"image_content_url" : img})
112
  .execute())
113
 
114
  def update_post(self,ids,idd):
timing_lin.py CHANGED
@@ -21,7 +21,7 @@ def format_slot(dt):
21
 
22
  def add_request(df_fixed, new_slot_str):
23
  new_dt = parse_slot(new_slot_str)
24
- desired_dt = new_dt - timedelta(minutes=7)
25
 
26
  # on récupère les adjusted existants triés
27
  slots_dt = sorted(parse_slot(s) for s in df_fixed['adjusted_time'])
@@ -34,21 +34,21 @@ def add_request(df_fixed, new_slot_str):
34
  conflict = False
35
 
36
  # 1) avec le schedule_time
37
- if new_dt - final_dt < timedelta(minutes=10):
38
- # on recule pour avoir exactement 7min d'avance
39
- final_dt = new_dt - timedelta(minutes=10)
40
  conflict = True
41
 
42
  # 2) avec chaque adjusted existant
43
  for slot in slots_dt:
44
- if abs((slot - final_dt).total_seconds()) < 10*60:
45
  # on recule de 7min par rapport à cet adjusted
46
  # si slot > final, on repousse final à slot -7min
47
  # sinon (slot < final), on recule encore de 7min
48
  if slot > final_dt:
49
- final_dt = slot - timedelta(minutes=10)
50
  else:
51
- final_dt = slot - timedelta(minutes=10)
52
  conflict = True
53
  break
54
 
 
21
 
22
  def add_request(df_fixed, new_slot_str):
23
  new_dt = parse_slot(new_slot_str)
24
+ desired_dt = new_dt - timedelta(minutes=5)
25
 
26
  # on récupère les adjusted existants triés
27
  slots_dt = sorted(parse_slot(s) for s in df_fixed['adjusted_time'])
 
34
  conflict = False
35
 
36
  # 1) avec le schedule_time
37
+ if new_dt - final_dt < timedelta(minutes=5):
38
+ # on recule pour avoir exactement 7mi d'avance
39
+ final_dt = new_dt - timedelta(minutes=5)
40
  conflict = True
41
 
42
  # 2) avec chaque adjusted existant
43
  for slot in slots_dt:
44
+ if abs((slot - final_dt).total_seconds()) < 5*60:
45
  # on recule de 7min par rapport à cet adjusted
46
  # si slot > final, on repousse final à slot -7min
47
  # sinon (slot < final), on recule encore de 7min
48
  if slot > final_dt:
49
+ final_dt = slot - timedelta(minutes=5)
50
  else:
51
+ final_dt = slot - timedelta(minutes=5)
52
  conflict = True
53
  break
54