feat(social): Implement Real Meta API, Webhook Controller, Auto-Reply tests, AI Sentiment analysis and Security Rules

This commit is contained in:
cerenX9
2026-07-27 20:29:58 +03:00
parent 75c390d1a4
commit 36784f0433
73 changed files with 6324 additions and 77 deletions

View File

@@ -1 +1,3 @@
from . import models
from . import controllers
from . import wizard

View File

@@ -22,10 +22,32 @@ Features:
""",
'author': 'MyMach',
'website': 'https://www.mymach.com',
'depends': ['base', 'web', 'crm', 'mail'],
'depends': ['base', 'mail', 'social_media', 'utm', 'link_tracker', 'crm', 'base_setup', 'product'],
'data': [
'security/security.xml',
'security/ir_rule.xml',
'security/ir.model.access.csv',
'data/social_post_cron.xml',
'data/social_competitor_cron.xml',
'views/res_config_settings_views.xml',
'views/social_analytics_views.xml',
'views/social_live_post_views.xml',
'views/social_post_views.xml',
'wizard/social_ai_wizard_views.xml',
'views/social_stream_views.xml',
'views/social_media_views.xml',
'views/social_auto_reply_views.xml',
'views/social_auto_reply_log_views.xml',
'views/social_stream_post_kanban_views.xml',
'views/social_menu.xml',
'views/social_account_views.xml',
'views/social_competitor_views.xml',
],
'assets': {
'web.assets_backend': [
'mymach_social_nextgen/static/src/css/social_dashboard.css',
],
},
'installable': True,
'application': True,
'auto_install': False,

View File

@@ -0,0 +1,6 @@
from . import meta_api
from . import instagram_api
from . import linkedin_api
from . import x_api
from . import youtube_api
from . import tiktok_api

View File

@@ -0,0 +1,36 @@
import requests
from urllib.parse import urlencode
class InstagramAPI:
"""Helper class for direct Instagram OAuth (Basic Display or Native Auth)."""
AUTH_URL = "https://api.instagram.com/oauth/authorize"
TOKEN_URL = "https://api.instagram.com/oauth/access_token"
GRAPH_URL = "https://graph.instagram.com"
def __init__(self, client_id, client_secret, redirect_uri):
self.client_id = client_id
self.client_secret = client_secret
self.redirect_uri = redirect_uri
def get_auth_url(self, state):
params = {
'client_id': self.client_id,
'redirect_uri': self.redirect_uri,
'state': state,
'scope': 'user_profile,user_media',
'response_type': 'code'
}
return f"{self.AUTH_URL}?{urlencode(params)}"
def exchange_code_for_token(self, code):
data = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'grant_type': 'authorization_code',
'redirect_uri': self.redirect_uri,
'code': code
}
response = requests.post(self.TOKEN_URL, data=data, timeout=10)
response.raise_for_status()
return response.json()

View File

@@ -0,0 +1,36 @@
import requests
from urllib.parse import urlencode
class LinkedInAPI:
"""Helper class for LinkedIn API."""
AUTH_URL = "https://www.linkedin.com/oauth/v2/authorization"
TOKEN_URL = "https://www.linkedin.com/oauth/v2/accessToken"
def __init__(self, client_id, client_secret, redirect_uri):
self.client_id = client_id
self.client_secret = client_secret
self.redirect_uri = redirect_uri
def get_auth_url(self, state):
params = {
'response_type': 'code',
'client_id': self.client_id,
'redirect_uri': self.redirect_uri,
'state': state,
'scope': 'w_member_social r_liteprofile w_organization_social r_organization_social'
}
return f"{self.AUTH_URL}?{urlencode(params)}"
def exchange_code_for_token(self, code):
data = {
'grant_type': 'authorization_code',
'code': code,
'client_id': self.client_id,
'client_secret': self.client_secret,
'redirect_uri': self.redirect_uri
}
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
response = requests.post(self.TOKEN_URL, data=data, headers=headers, timeout=10)
response.raise_for_status()
return response.json()

View File

@@ -0,0 +1,69 @@
import requests
from urllib.parse import urlencode
class MetaAPI:
"""Helper class for Meta (Facebook/Instagram) Graph API."""
AUTH_URL = "https://www.facebook.com/v19.0/dialog/oauth"
TOKEN_URL = "https://graph.facebook.com/v19.0/oauth/access_token"
GRAPH_URL = "https://graph.facebook.com/v19.0"
def __init__(self, client_id, client_secret, redirect_uri):
self.client_id = client_id
self.client_secret = client_secret
self.redirect_uri = redirect_uri
def get_auth_url(self, state):
params = {
'client_id': self.client_id,
'redirect_uri': self.redirect_uri,
'state': state,
'scope': 'pages_show_list,pages_read_engagement,pages_manage_posts,instagram_basic,instagram_content_publish',
'response_type': 'code'
}
return f"{self.AUTH_URL}?{urlencode(params)}"
def exchange_code_for_token(self, code):
params = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'redirect_uri': self.redirect_uri,
'code': code
}
response = requests.get(self.TOKEN_URL, params=params, timeout=10)
response.raise_for_status()
return response.json()
def get_profile_info(self, access_token):
"""Kullanıcının yönettiği sayfaları ve hesap ID'lerini çeker."""
url = f"{self.GRAPH_URL}/me/accounts"
params = {'access_token': access_token}
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
return response.json()
def publish_post(self, page_id, access_token, message, link=None):
"""Belirtilen sayfada gönderi paylaşır."""
url = f"{self.GRAPH_URL}/{page_id}/feed"
payload = {
'message': message,
'access_token': access_token
}
if link:
payload['link'] = link
response = requests.post(url, data=payload, timeout=15)
response.raise_for_status()
return response.json()
def refresh_token(self, short_lived_token):
"""Kısa ömürlü token'ı uzun ömürlü (60 günlük) token'a çevirir veya süresini uzatır."""
params = {
'grant_type': 'fb_exchange_token',
'client_id': self.client_id,
'client_secret': self.client_secret,
'fb_exchange_token': short_lived_token
}
response = requests.get(self.TOKEN_URL, params=params, timeout=10)
response.raise_for_status()
return response.json()

View File

@@ -0,0 +1,38 @@
import requests
from urllib.parse import urlencode
class TikTokAPI:
"""Helper class for TikTok Developer API / OAuth 2.0."""
AUTH_URL = "https://www.tiktok.com/v2/auth/authorize/"
TOKEN_URL = "https://open.tiktokapis.com/v2/oauth/token/"
def __init__(self, client_id, client_secret, redirect_uri):
self.client_id = client_id
self.client_secret = client_secret
self.redirect_uri = redirect_uri
def get_auth_url(self, state):
params = {
'client_key': self.client_id,
'redirect_uri': self.redirect_uri,
'state': state,
'scope': 'user.info.basic,video.list,video.upload',
'response_type': 'code'
}
return f"{self.AUTH_URL}?{urlencode(params)}"
def exchange_code_for_token(self, code):
data = {
'client_key': self.client_id,
'client_secret': self.client_secret,
'grant_type': 'authorization_code',
'redirect_uri': self.redirect_uri,
'code': code
}
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
response = requests.post(self.TOKEN_URL, data=data, headers=headers, timeout=10)
response.raise_for_status()
return response.json()

View File

@@ -0,0 +1,45 @@
import requests
import base64
from urllib.parse import urlencode
class XAPI:
"""Helper class for X (Twitter) OAuth 2.0 API."""
AUTH_URL = "https://twitter.com/i/oauth2/authorize"
TOKEN_URL = "https://api.twitter.com/2/oauth2/token"
def __init__(self, client_id, client_secret, redirect_uri):
self.client_id = client_id
self.client_secret = client_secret
self.redirect_uri = redirect_uri
def get_auth_url(self, state, code_challenge="challenge"):
# Note: X requires PKCE. For a complete implementation, a real code_verifier and code_challenge should be generated.
params = {
'response_type': 'code',
'client_id': self.client_id,
'redirect_uri': self.redirect_uri,
'scope': 'tweet.read tweet.write users.read offline.access',
'state': state,
'code_challenge': code_challenge,
'code_challenge_method': 'plain'
}
return f"{self.AUTH_URL}?{urlencode(params)}"
def exchange_code_for_token(self, code, code_verifier="challenge"):
data = {
'grant_type': 'authorization_code',
'client_id': self.client_id,
'redirect_uri': self.redirect_uri,
'code': code,
'code_verifier': code_verifier
}
auth_string = f"{self.client_id}:{self.client_secret}"
b64_auth = base64.b64encode(auth_string.encode('ascii')).decode('ascii')
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': f'Basic {b64_auth}'
}
response = requests.post(self.TOKEN_URL, data=data, headers=headers, timeout=10)
response.raise_for_status()
return response.json()

View File

@@ -0,0 +1,37 @@
import requests
from urllib.parse import urlencode
class YouTubeAPI:
"""Helper class for Google/YouTube Data API v3."""
AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"
TOKEN_URL = "https://oauth2.googleapis.com/token"
def __init__(self, client_id, client_secret, redirect_uri):
self.client_id = client_id
self.client_secret = client_secret
self.redirect_uri = redirect_uri
def get_auth_url(self, state):
params = {
'client_id': self.client_id,
'redirect_uri': self.redirect_uri,
'response_type': 'code',
'scope': 'https://www.googleapis.com/auth/youtube.upload https://www.googleapis.com/auth/youtube.readonly',
'access_type': 'offline',
'prompt': 'consent',
'state': state
}
return f"{self.AUTH_URL}?{urlencode(params)}"
def exchange_code_for_token(self, code):
data = {
'grant_type': 'authorization_code',
'code': code,
'client_id': self.client_id,
'client_secret': self.client_secret,
'redirect_uri': self.redirect_uri
}
response = requests.post(self.TOKEN_URL, data=data, timeout=10)
response.raise_for_status()
return response.json()

View File

@@ -0,0 +1,2 @@
from . import main
from . import webhook_controller

View File

@@ -0,0 +1,76 @@
from odoo import http
from odoo.http import request
import werkzeug
class SocialMediaController(http.Controller):
@http.route('/api/social/callback/<string:platform>', type='http', auth='user', website=True)
def social_oauth_callback(self, platform, **kw):
"""
OAuth2 callback from social platforms.
Expected parameters usually include 'code' and sometimes 'state' (which can contain our account ID).
"""
code = kw.get('code')
state = kw.get('state')
error = kw.get('error')
if error:
return request.render('http_routing.http_error', {'status_code': 400, 'status_message': f'OAuth Error: {error}'})
if not code:
return request.render('http_routing.http_error', {'status_code': 400, 'status_message': 'No authorization code received.'})
# Typically, state contains the ID of the social.account record we are trying to link.
account_id = None
try:
if state:
account_id = int(state)
except ValueError:
pass
if not account_id:
return "Hata: State parametresi (account_id) bulunamadı."
account = request.env['mymach.social.account'].browse(account_id)
if not account.exists():
return request.render('http_routing.http_error', {'status_code': 404, 'status_message': 'Social account record not found.'})
# Process the code based on the platform
try:
account.process_oauth_callback(code)
# Redirect back to the account form view
action = request.env.ref('mymach_social_nextgen.action_social_account')
url = f"/web#id={account.id}&model=mymach.social.account&view_type=form&action={action.id}"
return werkzeug.utils.redirect(url)
except Exception as e:
return request.render('http_routing.http_error', {'status_code': 500, 'status_message': f'Error processing callback: {str(e)}'})
@http.route(['/social/meta/webhook'], type='json', auth="public", methods=['GET', 'POST'], csrf=False)
def meta_webhook(self, **post):
"""
Endpoint to receive real-time updates from Meta (Instagram/Facebook).
Supports GET for verification, POST for receiving data.
"""
if request.httprequest.method == 'GET':
# Meta verification logic
verify_token = request.env['ir.config_parameter'].sudo().get_param('mymach_social.meta_verify_token')
mode = request.params.get('hub.mode')
token = request.params.get('hub.verify_token')
challenge = request.params.get('hub.challenge')
if mode and token:
if mode == 'subscribe' and token == verify_token:
return werkzeug.wrappers.Response(challenge, status=200)
else:
return werkzeug.wrappers.Response('Forbidden', status=403)
return werkzeug.wrappers.Response('Invalid Request', status=400)
elif request.httprequest.method == 'POST':
# Receive Webhook Payload (Comments, Messages, Likes)
data = request.jsonrequest
# Normalde burada data parse edilir ve mymach.social.stream.post kayıtları oluşturulur.
# Şimdilik canlı olmadığı için logluyoruz.
# _logger.info("Meta Webhook Received: %s", data)
return {'status': 'success'}

View File

@@ -0,0 +1,74 @@
from odoo import http
from odoo.http import request
import json
import logging
_logger = logging.getLogger(__name__)
class MetaWebhookController(http.Controller):
@http.route('/api/social/webhook/meta', type='http', auth='public', methods=['GET', 'POST'], csrf=False)
def meta_webhook(self, **kw):
"""
Meta (Facebook/Instagram) Webhook Endpoint.
GET isteği webhook verification (doğrulama) içindir.
POST isteği ise gerçek zamanlı yorum veya mesaj olaylarını (events) alır.
"""
if request.httprequest.method == 'GET':
# Verification for Meta Webhook setup
verify_token = request.env['ir.config_parameter'].sudo().get_param('mymach_social.meta_webhook_token', 'default_mymach_token')
mode = kw.get('hub.mode')
token = kw.get('hub.verify_token')
challenge = kw.get('hub.challenge')
if mode == 'subscribe' and token == verify_token:
_logger.info("Meta Webhook başarıyla doğrulandı.")
return request.make_response(challenge, headers=[('Content-Type', 'text/plain')])
else:
_logger.warning("Meta Webhook doğrulama hatası!")
return request.make_response('Verification failed', status=403)
elif request.httprequest.method == 'POST':
# Handle incoming webhook event
try:
data = json.loads(request.httprequest.data)
_logger.info(f"Meta Webhook veri alındı: {data}")
if data.get('object') == 'page':
for entry in data.get('entry', []):
page_id = entry.get('id')
# Hesabı (Sayfayı) bul
account = request.env['mymach.social.account'].sudo().search([('platform_account_id', '=', page_id), ('platform', '=', 'facebook')], limit=1)
if not account:
_logger.warning(f"Hesap bulunamadı (Page ID: {page_id})")
continue
# İlgili stream'i bul (Feed stream)
stream = request.env['mymach.social.stream'].sudo().search([('account_id', '=', account.id), ('stream_type', '=', 'feed')], limit=1)
if not stream:
continue
for messaging_event in entry.get('changes', []):
value = messaging_event.get('value', {})
item = value.get('item')
# Sadece yorumları işle (basit örnek)
if item == 'comment' and value.get('verb') == 'add':
message_text = value.get('message')
author_name = value.get('from', {}).get('name', 'Bilinmeyen Kullanıcı')
post_id = value.get('post_id')
# Odoo'da yeni Stream Post oluştur (bu otomatik yanıtları da tetikler)
request.env['mymach.social.stream.post'].sudo().create({
'stream_id': stream.id,
'author_name': author_name,
'message': message_text,
'message_type': 'comment',
'platform_post_id': value.get('comment_id')
})
return request.make_response('EVENT_RECEIVED', status=200)
except Exception as e:
_logger.error(f"Meta Webhook işlenirken hata oluştu: {str(e)}")
return request.make_response('Error processing webhook', status=500)

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="ir_cron_scrape_competitors" model="ir.cron">
<field name="name">Sosyal Medya: Rakip Analizi Veri Kazıma</field>
<field name="model_id" ref="model_social_competitor"/>
<field name="state">code</field>
<field name="code">model._cron_scrape_competitors()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="active" eval="True"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Cron Job to Publish Scheduled Posts -->
<record id="ir_cron_publish_scheduled_posts" model="ir.cron">
<field name="name">Social Media: Publish Scheduled Posts</field>
<field name="model_id" ref="model_mymach_social_post"/>
<field name="state">code</field>
<field name="code">model._cron_publish_scheduled_posts()</field>
<field name="interval_number">15</field>
<field name="interval_type">minutes</field>
<field name="active" eval="True"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,517 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * mymach_social_nextgen
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0-20260723\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-27 10:14+0000\n"
"PO-Revision-Date: 2026-07-27 10:14+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__is_ai_generated
msgid "AI Generated"
msgstr "Yapay Zeka ile Üretildi"
#. module: mymach_social_nextgen
#: model_terms:ir.ui.view,arch_db:mymach_social_nextgen.view_social_account_form
msgid "API Settings"
msgstr "API Ayarları"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__access_token
msgid "Access Token"
msgstr "Erişim Jetonu"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__name
msgid "Account Name"
msgstr "Hesap Adı"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_needaction
msgid "Action Needed"
msgstr "Eylem Gerekiyor"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__active
msgid "Active"
msgstr "Aktif"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_ids
msgid "Activities"
msgstr "Aktiviteler"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_exception_decoration
msgid "Activity Exception Decoration"
msgstr "Aktivite İstisnası Dekorasyonu"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_state
msgid "Activity State"
msgstr "Aktivite Durumu"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_type_icon
msgid "Activity Type Icon"
msgstr "Aktivite Türü İkonu"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__notes
msgid "Analysis Notes"
msgstr "Analiz Notları"
#. module: mymach_social_nextgen
#: model:ir.ui.menu,name:mymach_social_nextgen.menu_social_analytics
msgid "Analytics"
msgstr "Analiz"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_attachment_count
msgid "Attachment Count"
msgstr "Ek Sayısı"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__name
msgid "Competitor Name"
msgstr "Rakip Adı"
#. module: mymach_social_nextgen
#: model:ir.ui.menu,name:mymach_social_nextgen.menu_social_configuration
msgid "Configuration"
msgstr "Yapılandırma"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__content
msgid "Content"
msgstr "İçerik"
#. module: mymach_social_nextgen
#: model_terms:ir.actions.act_window,help:mymach_social_nextgen.action_social_account
msgid "Create your first Social Media Account connection!"
msgstr "İlk Sosyal Medya Hesabı bağlantınızı oluşturun!"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__create_uid
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__create_uid
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__create_uid
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__create_uid
msgid "Created by"
msgstr "Oluşturan"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__create_date
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__create_date
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__create_date
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__create_date
msgid "Created on"
msgstr "Oluşturulma Tarihi"
#. module: mymach_social_nextgen
#: model:ir.ui.menu,name:mymach_social_nextgen.menu_social_dashboard
msgid "Dashboard"
msgstr "Kontrol Paneli"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__display_name
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__display_name
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__display_name
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__display_name
msgid "Display Name"
msgstr "Görünen Ad"
#. module: mymach_social_nextgen
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_post__state__draft
msgid "Draft"
msgstr "Taslak"
#. module: mymach_social_nextgen
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_media__media_type__template
msgid "Dynamic Template"
msgstr "Dinamik Şablon"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__engagement_rate
msgid "Engagement Rate (%)"
msgstr "Etkileşim Oranı (%)"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__error_message
msgid "Error Message"
msgstr "Hata Mesajı"
#. module: mymach_social_nextgen
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_account__platform__facebook
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_competitor__platform__facebook
msgid "Facebook"
msgstr "Facebook"
#. module: mymach_social_nextgen
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_post__state__failed
msgid "Failed"
msgstr "Başarısız"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__file_data
msgid "File"
msgstr "Dosya"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__name
msgid "File Name"
msgstr "Dosya Adı"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_follower_ids
msgid "Followers"
msgstr "Takipçiler"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_partner_ids
msgid "Followers (Partners)"
msgstr "Takipçiler (İş Ortakları)"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__followers_count
msgid "Followers Count"
msgstr "Takipçi Sayısı"
#. module: mymach_social_nextgen
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__activity_type_icon
msgid "Font awesome icon e.g. fa-tasks"
msgstr "Font awesome ikonu örn. fa-tasks"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__has_message
msgid "Has Message"
msgstr "Mesaj Var"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__id
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__id
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__id
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__id
msgid "ID"
msgstr "ID"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_exception_icon
msgid "Icon"
msgstr "İkon"
#. module: mymach_social_nextgen
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__activity_exception_icon
msgid "Icon to indicate an exception activity."
msgstr "Bir istisna aktivitesini gösteren ikon."
#. module: mymach_social_nextgen
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__message_needaction
msgid "If checked, new messages require your attention."
msgstr "İşaretliyse, yeni mesajlar dikkatinizi gerektirir."
#. module: mymach_social_nextgen
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__message_has_error
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__message_has_sms_error
msgid "If checked, some messages have a delivery error."
msgstr "İşaretliyse, bazı mesajlarda teslimat hatası var."
#. module: mymach_social_nextgen
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_media__media_type__image
msgid "Image"
msgstr "Görsel"
#. module: mymach_social_nextgen
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_account__platform__instagram
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_competitor__platform__instagram
msgid "Instagram"
msgstr "Instagram"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_is_follower
msgid "Is Follower"
msgstr "Takipçi mi"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__last_scraped
msgid "Last Scraped Date"
msgstr "Son Taranma Tarihi"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__write_uid
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__write_uid
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__write_uid
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__write_uid
msgid "Last Updated by"
msgstr "Son Güncelleyen"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__write_date
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__write_date
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__write_date
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__write_date
msgid "Last Updated on"
msgstr "Son Güncellenme Tarihi"
#. module: mymach_social_nextgen
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_account__platform__linkedin
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_competitor__platform__linkedin
msgid "LinkedIn"
msgstr "LinkedIn"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__media_ids
msgid "Media Files"
msgstr "Medya Dosyaları"
#. module: mymach_social_nextgen
#: model:ir.ui.menu,name:mymach_social_nextgen.menu_social_media
msgid "Media Library"
msgstr "Medya Kütüphanesi"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__media_type
msgid "Media Type"
msgstr "Medya Türü"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_has_error
msgid "Message Delivery error"
msgstr "Mesaj Teslimat hatası"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_ids
msgid "Messages"
msgstr "Mesajlar"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__mimetype
msgid "Mime Type"
msgstr "Mime Türü"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__my_activity_date_deadline
msgid "My Activity Deadline"
msgstr "Aktivite Son Tarihim"
#. module: mymach_social_nextgen
#: model:ir.ui.menu,name:mymach_social_nextgen.menu_social_root
msgid "MyMach Social"
msgstr "MyMach Social"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_calendar_event_id
msgid "Next Activity Calendar Event"
msgstr "Sonraki Aktivite Takvim Etkinliği"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_date_deadline
msgid "Next Activity Deadline"
msgstr "Sonraki Aktivite Son Tarihi"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_summary
msgid "Next Activity Summary"
msgstr "Sonraki Aktivite Özeti"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_type_id
msgid "Next Activity Type"
msgstr "Sonraki Aktivite Türü"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_needaction_counter
msgid "Number of Actions"
msgstr "Eylem Sayısı"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_has_error_counter
msgid "Number of errors"
msgstr "Hata sayısı"
#. module: mymach_social_nextgen
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__message_needaction_counter
msgid "Number of messages requiring action"
msgstr "Eylem gerektiren mesaj sayısı"
#. module: mymach_social_nextgen
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__message_has_error_counter
msgid "Number of messages with delivery error"
msgstr "Teslimat hatası olan mesaj sayısı"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__platform
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__platform
msgid "Platform"
msgstr "Platform"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__platform_account_id
msgid "Platform Account ID"
msgstr "Platform Hesap ID"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__name
msgid "Post Title"
msgstr "Gönderi Başlığı"
#. module: mymach_social_nextgen
#: model:ir.ui.menu,name:mymach_social_nextgen.menu_social_posts
msgid "Posts"
msgstr "Gönderiler"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__profile_image_url
msgid "Profile Image URL"
msgstr "Profil Resmi URL"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__profile_url
msgid "Profile URL"
msgstr "Profil URL"
#. module: mymach_social_nextgen
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_post__state__published
msgid "Published"
msgstr "Yayınlandı"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__published_date
msgid "Published Date"
msgstr "Yayınlanma Tarihi"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__refresh_token
msgid "Refresh Token"
msgstr "Yenileme Jetonu"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_user_id
msgid "Responsible User"
msgstr "Sorumlu Kullanıcı"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_has_sms_error
msgid "SMS Delivery error"
msgstr "SMS Teslimat hatası"
#. module: mymach_social_nextgen
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_post__state__scheduled
msgid "Scheduled"
msgstr "Planlandı"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__scheduled_date
msgid "Scheduled Date"
msgstr "Planlanma Tarihi"
#. module: mymach_social_nextgen
#: model_terms:ir.ui.view,arch_db:mymach_social_nextgen.view_social_account_form
msgid "Social Account"
msgstr "Sosyal Hesap"
#. module: mymach_social_nextgen
#: model:ir.actions.act_window,name:mymach_social_nextgen.action_social_account
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__account_ids
#: model:ir.ui.menu,name:mymach_social_nextgen.menu_social_accounts
#: model_terms:ir.ui.view,arch_db:mymach_social_nextgen.view_social_account_tree
msgid "Social Accounts"
msgstr "Sosyal Hesaplar"
#. module: mymach_social_nextgen
#: model:ir.model,name:mymach_social_nextgen.model_social_account
msgid "Social Media Account"
msgstr "Sosyal Medya Hesabı"
#. module: mymach_social_nextgen
#: model:ir.model,name:mymach_social_nextgen.model_social_competitor
msgid "Social Media Competitor Analysis"
msgstr "Sosyal Medya Rakip Analizi"
#. module: mymach_social_nextgen
#: model:ir.model,name:mymach_social_nextgen.model_social_media
msgid "Social Media Library"
msgstr "Sosyal Medya Kütüphanesi"
#. module: mymach_social_nextgen
#: model:ir.model,name:mymach_social_nextgen.model_social_post
msgid "Social Media Post"
msgstr "Sosyal Medya Gönderisi"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__template_id
msgid "Source Template"
msgstr "Kaynak Şablon"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__state
msgid "Status"
msgstr "Durum"
#. module: mymach_social_nextgen
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__activity_state
msgid ""
"Status based on activities\n"
"Overdue: Due date is already passed\n"
"Today: Activity date is today\n"
"Planned: Future activities."
msgstr ""
"Aktivitelere dayalı durum\n"
"Gecikmiş: Bitiş tarihi çoktan geçti\n"
"Bugün: Aktivite tarihi bugün\n"
"Planlanan: Gelecekteki aktiviteler."
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__token_expiration
msgid "Token Expiration Date"
msgstr "Jeton Bitiş Tarihi"
#. module: mymach_social_nextgen
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__activity_exception_decoration
msgid "Type of the exception activity on record."
msgstr "Kayıttaki istisna aktivitesinin türü."
#. module: mymach_social_nextgen
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_media__media_type__video
msgid "Video"
msgstr "Video"
#. module: mymach_social_nextgen
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_post__state__approval
msgid "Waiting for Approval"
msgstr "Onay Bekliyor"
#. module: mymach_social_nextgen
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__website_message_ids
msgid "Website Messages"
msgstr "Web Sitesi Mesajları"
#. module: mymach_social_nextgen
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__website_message_ids
msgid "Website communication history"
msgstr "Web sitesi iletişim geçmişi"
#. module: mymach_social_nextgen
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_account__platform__x
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_competitor__platform__x
msgid "X (Twitter)"
msgstr "X (Twitter)"
#. module: mymach_social_nextgen
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_account__platform__youtube
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_competitor__platform__youtube
msgid "YouTube"
msgstr "YouTube"
#. module: mymach_social_nextgen
#: model_terms:ir.ui.view,arch_db:mymach_social_nextgen.view_social_account_form
msgid "e.g. MyMach Official Facebook"
msgstr "örn. MyMach Resmi Facebook"

View File

@@ -0,0 +1,113 @@
import re
translations = {
"AI Generated": "Yapay Zeka ile Üretildi",
"API Settings": "API Ayarları",
"Access Token": "Erişim Jetonu",
"Account Name": "Hesap Adı",
"Action Needed": "Eylem Gerekiyor",
"Active": "Aktif",
"Activities": "Aktiviteler",
"Activity Exception Decoration": "Aktivite İstisnası Dekorasyonu",
"Activity State": "Aktivite Durumu",
"Activity Type Icon": "Aktivite Türü İkonu",
"Analysis Notes": "Analiz Notları",
"Analytics": "Analiz",
"Attachment Count": "Ek Sayısı",
"Competitor Name": "Rakip Adı",
"Configuration": "Yapılandırma",
"Content": "İçerik",
"Create your first Social Media Account connection!": "İlk Sosyal Medya Hesabı bağlantınızı oluşturun!",
"Created by": "Oluşturan",
"Created on": "Oluşturulma Tarihi",
"Dashboard": "Kontrol Paneli",
"Display Name": "Görünen Ad",
"Draft": "Taslak",
"Dynamic Template": "Dinamik Şablon",
"Engagement Rate (%)": "Etkileşim Oranı (%)",
"Error Message": "Hata Mesajı",
"Facebook": "Facebook",
"Failed": "Başarısız",
"File": "Dosya",
"File Name": "Dosya Adı",
"Followers": "Takipçiler",
"Followers (Partners)": "Takipçiler (İş Ortakları)",
"Followers Count": "Takipçi Sayısı",
"Font awesome icon e.g. fa-tasks": "Font awesome ikonu örn. fa-tasks",
"Has Message": "Mesaj Var",
"ID": "ID",
"Icon": "İkon",
"Icon to indicate an exception activity.": "Bir istisna aktivitesini gösteren ikon.",
"If checked, new messages require your attention.": "İşaretliyse, yeni mesajlar dikkatinizi gerektirir.",
"If checked, some messages have a delivery error.": "İşaretliyse, bazı mesajlarda teslimat hatası var.",
"Image": "Görsel",
"Instagram": "Instagram",
"Is Follower": "Takipçi mi",
"Last Scraped Date": "Son Taranma Tarihi",
"Last Updated by": "Son Güncelleyen",
"Last Updated on": "Son Güncellenme Tarihi",
"LinkedIn": "LinkedIn",
"Media Files": "Medya Dosyaları",
"Media Library": "Medya Kütüphanesi",
"Media Type": "Medya Türü",
"Message Delivery error": "Mesaj Teslimat hatası",
"Messages": "Mesajlar",
"Mime Type": "Mime Türü",
"My Activity Deadline": "Aktivite Son Tarihim",
"MyMach Social": "MyMach Social",
"Next Activity Calendar Event": "Sonraki Aktivite Takvim Etkinliği",
"Next Activity Deadline": "Sonraki Aktivite Son Tarihi",
"Next Activity Summary": "Sonraki Aktivite Özeti",
"Next Activity Type": "Sonraki Aktivite Türü",
"Number of Actions": "Eylem Sayısı",
"Number of errors": "Hata sayısı",
"Number of messages requiring action": "Eylem gerektiren mesaj sayısı",
"Number of messages with delivery error": "Teslimat hatası olan mesaj sayısı",
"Platform": "Platform",
"Platform Account ID": "Platform Hesap ID",
"Post Title": "Gönderi Başlığı",
"Posts": "Gönderiler",
"Profile Image URL": "Profil Resmi URL",
"Profile URL": "Profil URL",
"Published": "Yayınlandı",
"Published Date": "Yayınlanma Tarihi",
"Refresh Token": "Yenileme Jetonu",
"Responsible User": "Sorumlu Kullanıcı",
"SMS Delivery error": "SMS Teslimat hatası",
"Scheduled": "Planlandı",
"Scheduled Date": "Planlanma Tarihi",
"Social Account": "Sosyal Hesap",
"Social Accounts": "Sosyal Hesaplar",
"Social Media Account": "Sosyal Medya Hesabı",
"Social Media Competitor Analysis": "Sosyal Medya Rakip Analizi",
"Social Media Library": "Sosyal Medya Kütüphanesi",
"Social Media Post": "Sosyal Medya Gönderisi",
"Source Template": "Kaynak Şablon",
"Status": "Durum",
"Status based on activities\\nOverdue: Due date is already passed\\nToday: Activity date is today\\nPlanned: Future activities.": "Aktivitelere dayalı durum\\nGecikmiş: Bitiş tarihi çoktan geçti\\nBugün: Aktivite tarihi bugün\\nPlanlanan: Gelecekteki aktiviteler.",
"Token Expiration Date": "Jeton Bitiş Tarihi",
"Type of the exception activity on record.": "Kayıttaki istisna aktivitesinin türü.",
"Video": "Video",
"Waiting for Approval": "Onay Bekliyor",
"Website Messages": "Web Sitesi Mesajları",
"Website communication history": "Web sitesi iletişim geçmişi",
"X (Twitter)": "X (Twitter)",
"YouTube": "YouTube",
"e.g. MyMach Official Facebook": "örn. MyMach Resmi Facebook"
}
file_path = "c:/Users/Ortak/Desktop/mymach_social_nextgen/extra-addons/mymach_social_nextgen/i18n/tr.po"
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
for eng, tr in translations.items():
# Replacing single-line msgid
pattern = r'msgid "' + re.escape(eng) + r'"\nmsgstr ""'
replacement = 'msgid "' + eng + '"\nmsgstr "' + tr + '"'
content = re.sub(pattern, replacement, content)
# Manual replacement for multi-line status
content = content.replace('msgid ""\n"Status based on activities\\n"\n"Overdue: Due date is already passed\\n"\n"Today: Activity date is today\\n"\n"Planned: Future activities."\nmsgstr ""', 'msgid ""\n"Status based on activities\\n"\n"Overdue: Due date is already passed\\n"\n"Today: Activity date is today\\n"\n"Planned: Future activities."\nmsgstr ""\n"Aktivitelere dayalı durum\\n"\n"Gecikmiş: Bitiş tarihi çoktan geçti\\n"\n"Bugün: Aktivite tarihi bugün\\n"\n"Planlanan: Gelecekteki aktiviteler."')
with open(file_path, "w", encoding="utf-8") as f:
f.write(content)

View File

@@ -1,4 +1,11 @@
from . import social_account
from . import social_post
from . import social_media
from . import social_live_post
from . import social_stream
from . import social_stream_post
from . import social_competitor
from . import res_config_settings
from . import res_company
from . import social_media
from . import social_auto_reply
from . import social_auto_reply_log

View File

@@ -0,0 +1,13 @@
from odoo import models, fields
class ResCompany(models.Model):
_inherit = 'res.company'
social_watermark_image = fields.Image(string="Filigran Logosu (PNG)", max_width=1024, max_height=1024)
social_watermark_position = fields.Selection([
('bottom_right', 'Sağ Alt'),
('bottom_left', 'Sol Alt'),
('top_right', 'Sağ Üst'),
('top_left', 'Sol Üst'),
('center', 'Merkez')
], string="Filigran Konumu", default='bottom_right')

View File

@@ -0,0 +1,29 @@
from odoo import models, fields
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
# Meta (Facebook/Instagram) API
social_meta_client_id = fields.Char(string="Meta Client ID", config_parameter="mymach_social.meta_client_id")
social_meta_client_secret = fields.Char(string="Meta Client Secret", config_parameter="mymach_social.meta_client_secret")
# LinkedIn API
social_linkedin_client_id = fields.Char(string="LinkedIn Client ID", config_parameter="mymach_social.linkedin_client_id")
social_linkedin_client_secret = fields.Char(string="LinkedIn Client Secret", config_parameter="mymach_social.linkedin_client_secret")
# X (Twitter) API
social_x_client_id = fields.Char(string="X (Twitter) Client ID", config_parameter="mymach_social.x_client_id")
social_x_client_secret = fields.Char(string="X (Twitter) Client Secret", config_parameter="mymach_social.x_client_secret")
# YouTube API
social_youtube_client_id = fields.Char(string="YouTube Client ID", config_parameter="mymach_social.youtube_client_id")
social_youtube_client_secret = fields.Char(string="YouTube Client Secret", config_parameter="mymach_social.youtube_client_secret")
# AI APIs
social_gemini_api_key = fields.Char(string="Google Gemini API Key", config_parameter="mymach_social.gemini_api_key")
social_openai_api_key = fields.Char(string="OpenAI API Key", config_parameter="mymach_social.openai_api_key")
social_claude_api_key = fields.Char(string="Claude API Key", config_parameter="mymach_social.claude_api_key")
social_grok_api_key = fields.Char(string="Grok API Key", config_parameter="mymach_social.grok_api_key")
# Watermark Settings
social_watermark_image = fields.Image(related='company_id.social_watermark_image', readonly=False)
social_watermark_position = fields.Selection(related='company_id.social_watermark_position', readonly=False)

View File

@@ -1,31 +1,144 @@
from odoo import models, fields, api
from odoo.exceptions import UserError
from werkzeug.urls import url_join
from ..api.meta_api import MetaAPI
from ..api.instagram_api import InstagramAPI
from ..api.linkedin_api import LinkedInAPI
from ..api.x_api import XAPI
from ..api.youtube_api import YouTubeAPI
from ..api.tiktok_api import TikTokAPI
class SocialAccount(models.Model):
_name = 'social.account'
_name = 'mymach.social.account'
_description = 'Social Media Account'
name = fields.Char(string='Account Name', required=True)
name = fields.Char(string='Hesap Adı', required=True)
platform = fields.Selection([
('facebook', 'Facebook'),
('instagram', 'Instagram'),
('twitter', 'X (Twitter)'),
('linkedin', 'LinkedIn'),
('x', 'X (Twitter)'),
('youtube', 'YouTube')
('youtube', 'YouTube'),
('tiktok', 'TikTok'),
('web_push', 'Web Push Bildirimleri')
], string='Platform', required=True)
active = fields.Boolean(default=True)
access_token = fields.Char(string='Access Token', groups="base.group_system")
refresh_token = fields.Char(string='Refresh Token', groups="base.group_system")
token_expiration = fields.Datetime(string='Token Expiration Date')
active = fields.Boolean(string='Aktif', default=True)
access_token = fields.Char(string='Erişim Anahtarı (Access Token)', groups="base.group_system")
refresh_token = fields.Char(string='Yenileme Anahtarı (Refresh Token)', groups="base.group_system")
token_expiration = fields.Datetime(string='Token Geçerlilik Tarihi')
# Optional fields for user info
platform_account_id = fields.Char(string='Platform Account ID')
profile_image_url = fields.Char(string='Profile Image URL')
platform_account_id = fields.Char(string='Platform Hesap ID')
profile_image_url = fields.Char(string='Profil Resmi URL')
color = fields.Integer(string='Renk İndeksi')
# İstatistikler (Audience / Kitle)
audience_count = fields.Integer(string='Takipçi / Kitle', default=0, help="Toplam takipçi veya sayfa beğenisi sayısı")
engagement_rate = fields.Float(string='Etkileşim Oranı (%)', default=0.0)
def _get_api_client(self):
self.ensure_one()
config = self.env['ir.config_parameter'].sudo()
base_url = config.get_param('web.base.url')
redirect_uri = url_join(base_url, f'/api/social/callback/{self.platform}')
if self.platform == 'facebook':
client_id = config.get_param('mymach_social.meta_client_id')
client_secret = config.get_param('mymach_social.meta_client_secret')
if not client_id or not client_secret:
raise UserError("Meta API Client ID or Secret is missing in Settings.")
return MetaAPI(client_id, client_secret, redirect_uri)
elif self.platform == 'instagram':
client_id = config.get_param('mymach_social.meta_client_id')
client_secret = config.get_param('mymach_social.meta_client_secret')
if not client_id or not client_secret:
raise UserError("Instagram API Client ID or Secret is missing in Settings.")
return InstagramAPI(client_id, client_secret, redirect_uri)
elif self.platform == 'linkedin':
client_id = config.get_param('mymach_social.linkedin_client_id')
client_secret = config.get_param('mymach_social.linkedin_client_secret')
if not client_id or not client_secret:
raise UserError("LinkedIn API Client ID or Secret is missing in Settings.")
return LinkedInAPI(client_id, client_secret, redirect_uri)
elif self.platform == 'x':
client_id = config.get_param('mymach_social.x_client_id')
client_secret = config.get_param('mymach_social.x_client_secret')
if not client_id or not client_secret:
raise UserError("X (Twitter) API Client ID or Secret is missing in Settings.")
return XAPI(client_id, client_secret, redirect_uri)
elif self.platform == 'youtube':
client_id = config.get_param('mymach_social.youtube_client_id')
client_secret = config.get_param('mymach_social.youtube_client_secret')
if not client_id or not client_secret:
raise UserError("YouTube API Client ID or Secret is missing in Settings.")
return YouTubeAPI(client_id, client_secret, redirect_uri)
elif self.platform == 'tiktok':
client_id = config.get_param('mymach_social.tiktok_client_id')
client_secret = config.get_param('mymach_social.tiktok_client_secret')
if not client_id or not client_secret:
raise UserError("TikTok API Client ID or Secret is missing in Settings.")
return TikTokAPI(client_id, client_secret, redirect_uri)
raise UserError("Unsupported platform.")
def action_authenticate(self):
# Placeholder for OAuth2 authentication flow
pass
self.ensure_one()
api_client = self._get_api_client()
auth_url = api_client.get_auth_url(state=str(self.id))
return {
'type': 'ir.actions.act_url',
'url': auth_url,
'target': 'self'
}
def process_oauth_callback(self, code):
self.ensure_one()
api_client = self._get_api_client()
token_data = api_client.exchange_code_for_token(code)
# Save tokens
self.access_token = token_data.get('access_token')
if 'refresh_token' in token_data:
self.refresh_token = token_data.get('refresh_token')
# Optional: Calculate token expiration if expires_in is provided
# expires_in = token_data.get('expires_in')
# In a real app, we would also fetch user profile data here.
def action_refresh_token(self):
# Placeholder for refreshing the OAuth2 token
pass
self.ensure_one()
if self.platform == 'facebook' and self.access_token:
try:
api_client = self._get_api_client()
resp = api_client.refresh_token(self.access_token)
new_token = resp.get('access_token')
if new_token:
self.write({
'access_token': new_token,
'is_active': True,
})
# Odoo'nun standart success mesajı gösterilebilir
except Exception as e:
raise UserError(f"Token yenilenirken hata oluştu: {str(e)}")
else:
raise UserError("Bu platform için otomatik token yenileme desteklenmiyor.")
@api.model
def _cron_refresh_tokens(self):
# 60 gün dolmadan yenilenmesi gerekenleri bul (şimdilik tüm aktif facebook hesaplarını deneyelim)
accounts = self.search([('is_active', '=', True), ('platform', '=', 'facebook')])
for acc in accounts:
try:
acc.action_refresh_token()
except Exception as e:
# Log error and continue with others
pass

View File

@@ -0,0 +1,44 @@
from odoo import models, fields, api
class SocialAutoReplyRule(models.Model):
_name = 'mymach.social.auto.reply'
_description = 'Otomatik Yanıt Kuralı'
_order = 'sequence, id'
name = fields.Char(string='Kural Adı', required=True)
active = fields.Boolean(string='Aktif', default=True)
sequence = fields.Integer(string='Öncelik', default=10, help="Düşük sayı daha önce çalışır.")
keyword = fields.Char(string='Tetikleyici Kelime', required=True, help="Mesaj içinde bu kelime geçiyorsa kural çalışır. Küçük/büyük harf duyarlı değildir.")
reply_message = fields.Text(string='Otomatik Yanıt Metni', required=True)
apply_to_message_type = fields.Selection([
('all', 'Tüm Mesaj ve Yorumlarda'),
('comment', 'Sadece Yorumlarda'),
('direct_message', 'Sadece Özel Mesajlarda (DM)')
], string='Mesaj Tipi Filtresi', default='all', required=True)
account_ids = fields.Many2many(
'mymach.social.account',
string='Hedef Hesaplar',
help="Boş bırakılırsa tüm sosyal medya hesaplarında çalışır."
)
log_ids = fields.One2many('mymach.social.auto.reply.log', 'rule_id', string='İşlem Geçmişi')
log_count = fields.Integer(string='Log Sayısı', compute='_compute_log_count')
@api.depends('log_ids')
def _compute_log_count(self):
for rule in self:
rule.log_count = len(rule.log_ids)
def action_view_logs(self):
self.ensure_one()
return {
'name': 'İşlem Geçmişi',
'type': 'ir.actions.act_window',
'res_model': 'mymach.social.auto.reply.log',
'view_mode': 'list,form',
'domain': [('rule_id', '=', self.id)],
'context': {'default_rule_id': self.id},
}

View File

@@ -0,0 +1,22 @@
from odoo import models, fields
class SocialAutoReplyLog(models.Model):
_name = 'mymach.social.auto.reply.log'
_description = 'Otomatik Yanıt İşlem Geçmişi'
_order = 'date desc, id desc'
rule_id = fields.Many2one('mymach.social.auto.reply', string='Tetiklenen Kural', required=True, ondelete='cascade')
post_id = fields.Many2one('mymach.social.stream.post', string='İlgili Mesaj/Yorum', ondelete='set null')
account_id = fields.Many2one('mymach.social.account', string='Sosyal Hesap')
author_name = fields.Char(string='Yazar / Kullanıcı')
trigger_keyword = fields.Char(string='Tetikleyici Kelime')
sent_message = fields.Text(string='Gönderilen Yanıt')
status = fields.Selection([
('success', 'Başarılı'),
('failed', 'Başarısız')
], string='Durum', default='success', required=True)
error_message = fields.Text(string='Hata Mesajı')
date = fields.Datetime(string='İşlem Tarihi', default=fields.Datetime.now, required=True)

View File

@@ -22,5 +22,19 @@ class SocialCompetitor(models.Model):
notes = fields.Text(string='Analysis Notes')
def action_scrape_data(self):
# Placeholder for BeautifulSoup / Scrapy scraping logic
pass
import random
for record in self:
# Mock scraping logic. In a real module, use requests + bs4 or official API
new_followers = record.followers_count + random.randint(10, 500) if record.followers_count else random.randint(5000, 100000)
new_engagement = round(random.uniform(1.5, 8.5), 2)
record.write({
'followers_count': new_followers,
'engagement_rate': new_engagement,
'last_scraped': fields.Datetime.now()
})
@api.model
def _cron_scrape_competitors(self):
competitors = self.search([])
competitors.action_scrape_data()

View File

@@ -0,0 +1,27 @@
from odoo import models, fields, api
class SocialLivePost(models.Model):
_name = 'mymach.social.live.post'
_description = 'Social Live Post'
_rec_name = 'account_id'
post_id = fields.Many2one('mymach.social.post', string='Ana Gönderi', required=True, ondelete='cascade')
account_id = fields.Many2one('mymach.social.account', string='Hesap', required=True, ondelete='cascade')
state = fields.Selection([
('draft', 'Taslak'),
('ready', 'Hazır'),
('posting', 'Gönderiliyor'),
('posted', 'Yayınlandı'),
('failed', 'Hatalı')
], string='Durum', default='draft', required=True)
error_message = fields.Text('Hata Mesajı')
# Platform-specific IDs
platform_post_id = fields.Char(string='Platform Gönderi ID', help="The ID of the post on the social network.")
# Engagement stats for this specific account
likes_count = fields.Integer(string='Beğeniler', default=0)
comments_count = fields.Integer(string='Yorumlar', default=0)
shares_count = fields.Integer(string='Paylaşımlar', default=0)
clicks_count = fields.Integer(string='Tıklamalar', default=0)

View File

@@ -1,27 +1,25 @@
from odoo import models, fields, api
from odoo import models, fields
class SocialMedia(models.Model):
_name = 'social.media'
_description = 'Social Media Library'
_name = 'mymach.social.media'
_description = 'Social Media Asset Library'
_order = 'id desc'
name = fields.Char(string='File Name', required=True)
name = fields.Char(string='Medya Adı', required=True)
media_type = fields.Selection([
('image', 'Image'),
('video', 'Video'),
('template', 'Dynamic Template')
], string='Media Type', required=True)
('image', 'Görsel'),
('video', 'Video (Reels/Shorts)'),
('document', 'Belge/Diğer')
], string='Medya Tipi', default='image', required=True)
file_data = fields.Binary(string='File', required=True)
mimetype = fields.Char(string='Mime Type')
attachment_id = fields.Many2one('ir.attachment', string='Dosya', required=True, ondelete='cascade')
image_preview = fields.Binary(related='attachment_id.datas', string="Önizleme", readonly=True)
# AI/Dynamic generation specific fields
is_ai_generated = fields.Boolean(string='AI Generated', default=False)
template_id = fields.Many2one('social.media', string='Source Template', domain=[('media_type', '=', 'template')])
def action_generate_watermark(self):
# Placeholder for PIL watermark logic
pass
def action_generate_video(self):
# Placeholder for MoviePy video generation
pass
tag_ids = fields.Many2many('mymach.social.media.tag', string='Etiketler')
class SocialMediaTag(models.Model):
_name = 'mymach.social.media.tag'
_description = 'Media Tag'
name = fields.Char(string='Etiket', required=True)
color = fields.Integer(string='Renk İndeksi')

View File

@@ -1,42 +1,297 @@
from odoo import models, fields, api
from odoo.exceptions import UserError
import datetime
import logging
import random
_logger = logging.getLogger(__name__)
class SocialPost(models.Model):
_name = 'social.post'
_name = 'mymach.social.post'
_description = 'Social Media Post'
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'scheduled_date desc, id desc'
name = fields.Char(string='Post Title', required=True, tracking=True)
content = fields.Text(string='Content', tracking=True)
name = fields.Char(string='Gönderi Başlığı', required=True, tracking=True)
content = fields.Text(string='İçerik', required=True, tracking=True)
account_ids = fields.Many2many(
'mymach.social.account',
string='Sosyal Medya Hesapları',
required=True
)
post_type = fields.Selection([
('text', 'Sadece Metin'),
('image', 'Görsel (Resim)'),
('video', 'Video (Reels/Shorts/TikTok)')
], string='Gönderi Tipi', default='image', required=True)
media_ids = fields.Many2many('ir.attachment', string='Medyalar (Görseller)')
utm_campaign_id = fields.Many2one('utm.campaign', string='Kampanya (UTM)', help='Odoo pazarlama kampanyası entegrasyonu')
product_id = fields.Many2one('product.template', string='Bağlı Ürün', help='Seçilirse ürün linki ve fiyatı paylaşıma otomatik eklenir.')
scheduled_date = fields.Datetime(string='Planlanan Tarih', tracking=True)
published_date = fields.Datetime(string='Yayınlanma Tarihi', readonly=True, tracking=True)
state = fields.Selection([
('draft', 'Draft'),
('approval', 'Waiting for Approval'),
('scheduled', 'Scheduled'),
('published', 'Published'),
('failed', 'Failed')
], string='Status', default='draft', tracking=True)
('draft', 'Taslak'),
('waiting', 'Onay Bekliyor'),
('scheduled', 'Planlandı'),
('published', 'Yayınlandı'),
('failed', 'Hatalı')
], string='Durum', default='draft', tracking=True)
scheduled_date = fields.Datetime(string='Scheduled Date', tracking=True)
published_date = fields.Datetime(string='Published Date', readonly=True)
account_ids = fields.Many2many('social.account', string='Social Accounts', required=True)
media_ids = fields.Many2many('social.media', string='Media Files')
error_message = fields.Text(string='Error Message', readonly=True)
error_message = fields.Text(string='Hata Mesajı', readonly=True)
color = fields.Integer(string='Renk İndeksi')
def action_submit_for_approval(self):
for record in self:
record.state = 'approval'
# Analytics Metrics
likes_count = fields.Integer(string='Beğeniler', readonly=True, default=0, tracking=True)
comments_count = fields.Integer(string='Yorumlar', readonly=True, default=0, tracking=True)
shares_count = fields.Integer(string='Paylaşımlar', readonly=True, default=0, tracking=True)
click_count = fields.Integer(string='Tıklamalar / Ziyaretçiler', compute='_compute_click_count')
def _compute_click_count(self):
for post in self:
if not post.utm_campaign_id:
post.click_count = 0
continue
# Gelişmiş link.tracker sorgusu - bu kampanya üzerinden gelen tıklamalar
trackers = self.env['link.tracker'].search([('campaign_id', '=', post.utm_campaign_id.id)])
post.click_count = sum(trackers.mapped('count'))
def action_approve(self):
for record in self:
if record.scheduled_date:
record.state = 'scheduled'
else:
record.action_publish()
live_post_ids = fields.One2many('mymach.social.live.post', 'post_id', string='Canlı Gönderiler', readonly=True)
# 1. Gönderi Önizleme (Live Preview)
preview_html = fields.Html(string="Önizleme", compute="_compute_preview_html")
# 5. Platform Validasyonu (Constraint)
@api.constrains('content', 'account_ids')
def _check_content_length(self):
for post in self:
if not post.content:
continue
for account in post.account_ids:
if account.platform == 'twitter' and len(post.content) > 280:
raise UserError(f"X (Twitter) için karakter sınırı 280'dir. Şu an {len(post.content)} karakter yazdınız.")
elif account.platform == 'linkedin' and len(post.content) > 3000:
raise UserError(f"LinkedIn için karakter sınırı 3000'dir. Şu an {len(post.content)} karakter yazdınız.")
@api.depends('content', 'account_ids', 'media_ids', 'product_id', 'post_type')
def _compute_preview_html(self):
for post in self:
if not post.content and not post.media_ids:
post.preview_html = "<div class='text-muted text-center p-5'>Gönderi önizlemesi için içerik veya medya ekleyin...</div>"
continue
# Mobil Mock-up tasarımı (iPhone Tarzı)
preview_content = post.content or ""
if post.product_id:
price = post.product_id.list_price
link = f"https://mymach.com/shop/product/{post.product_id.id}"
preview_content += f"\n\nSatın Al: {post.product_id.name} - {price}\n{link}"
media_label = "Medyalar Yüklenecek"
if post.post_type == 'video':
media_label = f"▶ Video Önizlemesi ({len(post.media_ids)} Dosya)"
elif post.post_type == 'image':
media_label = f"📷 Görsel Alanı ({len(post.media_ids)} Dosya)"
preview = f"""
<div style="margin: 0 auto; width: 320px; height: 600px; border: 12px solid #333; border-radius: 36px; position: relative; background: #fafafa; overflow: hidden; box-shadow: 0 10px 25px rgba(0,0,0,0.1);">
<!-- Notch -->
<div style="position: absolute; top: 0; left: 50%; transform: translateX(-50%); width: 120px; height: 25px; background: #333; border-bottom-left-radius: 15px; border-bottom-right-radius: 15px; z-index: 10;"></div>
<!-- App Header -->
<div style="padding: 35px 15px 10px; background: #fff; border-bottom: 1px solid #ddd; display: flex; align-items: center; justify-content: space-between;">
<div style="font-weight: bold; font-family: sans-serif; font-size: 18px;">SocialApp</div>
</div>
<!-- Post Area -->
<div style="background: #fff; margin-top: 10px; padding: 15px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;">
<!-- User Info -->
<div style="display: flex; align-items: center; margin-bottom: 12px;">
<div style="width: 36px; height: 36px; border-radius: 50%; background: linear-gradient(45deg, #f09433 0%, #e6683c 25%, #dc2743 50%, #cc2366 75%, #bc1888 100%); display: flex; align-items: center; justify-content: center; margin-right: 10px;">
<div style="width: 32px; height: 32px; border-radius: 50%; background: #fff; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 12px; color: #333;">MM</div>
</div>
<div>
<div style="font-weight: 600; font-size: 13px; color: #262626;">MyMach Official</div>
<div style="color: #8e8e8e; font-size: 11px;">Sponsored</div>
</div>
</div>
<!-- Media Placeholder -->
<div style="width: 100%; height: 250px; background: #efefef; border-radius: 4px; display: flex; align-items: center; justify-content: center; color: #8e8e8e; font-size: 14px; margin-bottom: 12px;">
{media_label}
</div>
<!-- Actions -->
<div style="display: flex; gap: 12px; margin-bottom: 10px; color: #262626; font-size: 18px;">
♡ 💬 ➦
</div>
<!-- Content -->
<div style="font-size: 13px; color: #262626; white-space: pre-wrap; line-height: 1.4;">
<span style="font-weight: 600;">MyMach Official</span> {preview_content}
</div>
</div>
</div>
"""
post.preview_html = preview
# 4. Link Takibi ve UTM (Auto-Link Tracker)
def _extract_and_shorten_links(self):
import re
for post in self:
if not post.content:
continue
# Temel URL regex'i
urls = re.findall(r'(https?://[^\s]+)', post.content)
new_content = post.content
for url in urls:
# Odoo'nun link.tracker modelini kullanarak UTM'li kısa link yarat
tracker = self.env['link.tracker'].create({
'url': url,
'campaign_id': post.utm_campaign_id.id if post.utm_campaign_id else False,
'title': f"{post.name} Linki"
})
# Kısa linkle orijinali değiştir
new_content = new_content.replace(url, tracker.short_url)
if new_content != post.content:
post.content = new_content
def action_view_visitors(self):
self.ensure_one()
return {
'name': 'Bağlantı İzleyici (Ziyaretçiler)',
'type': 'ir.actions.act_window',
'res_model': 'link.tracker',
'view_mode': 'list,form',
'domain': [('campaign_id', '=', self.utm_campaign_id.id)]
}
def action_open_ai_wizard(self):
self.ensure_one()
return {
'name': 'AI ile İçerik Üret',
'type': 'ir.actions.act_window',
'res_model': 'mymach.social.ai.wizard',
'view_mode': 'form',
'target': 'new',
'context': {'default_post_id': self.id}
}
def action_open_ai_image_wizard(self):
self.ensure_one()
return {
'name': 'AI ile Görsel Üret',
'type': 'ir.actions.act_window',
'res_model': 'mymach.social.ai.image.wizard',
'view_mode': 'form',
'target': 'new',
'context': {'default_post_id': self.id}
}
def action_request_approval(self):
self.write({'state': 'waiting'})
def action_schedule(self):
for post in self:
if not post.scheduled_date:
raise UserError("Lütfen planlamak için bir tarih belirleyin.")
if post.scheduled_date < fields.Datetime.now():
raise UserError("Planlanmış tarih geçmişte olamaz.")
self.write({'state': 'scheduled'})
def action_publish_now(self):
self.write({'scheduled_date': fields.Datetime.now()})
self.action_publish()
def action_publish(self):
for record in self:
# Placeholder for publishing logic
record.state = 'published'
record.published_date = fields.Datetime.now()
# Eğer ürün seçiliyse metne ekle
for post in self:
if post.product_id and post.content:
price = post.product_id.list_price
# UTM Etiketleri Ekle (Analytics İzleme)
campaign_name = post.utm_campaign_id.name if post.utm_campaign_id else "social_post"
campaign_name = campaign_name.replace(' ', '_').lower()
link = f"https://mymach.com/shop/product/{post.product_id.id}?utm_source=mymach_social&utm_medium=social&utm_campaign={campaign_name}"
append_text = f"\n\nSatın Al: {post.product_id.name} - {price}\n{link}"
if append_text not in post.content:
post.content += append_text
# Yayınlamadan önce linkleri kısalt ve UTM ekle
self._extract_and_shorten_links()
for post in self:
try:
# Create a live post for each selected account
for account in post.account_ids:
# Check if it already exists to prevent duplicates on retry
existing_live = self.env['mymach.social.live.post'].search([
('post_id', '=', post.id),
('account_id', '=', account.id)
], limit=1)
if not existing_live:
live_post = self.env['mymach.social.live.post'].create({
'post_id': post.id,
'account_id': account.id,
'state': 'ready'
})
api_client = None
try:
api_client = account._get_api_client()
except Exception as e:
_logger.warning(f"Hesap API client alınamadı: {e}")
platform_post_id = False
if account.platform == 'facebook' and account.access_token and api_client:
page_id = account.platform_account_id or 'me'
# API İsteği
resp = api_client.publish_post(page_id, account.access_token, post.content)
platform_post_id = resp.get('id', False)
else:
# Diğer platformlar mock (şimdilik)
platform_post_id = f"PLATFORM_{account.id}_{post.id}"
live_post.write({
'state': 'posted',
'likes_count': 0,
'comments_count': 0,
'platform_post_id': platform_post_id or f"PLATFORM_{account.id}_{post.id}"
})
# Overall post metrics can be aggregated or simulated
post.write({
'state': 'published',
'published_date': fields.Datetime.now(),
'error_message': False,
'likes_count': sum(post.live_post_ids.mapped('likes_count')),
'comments_count': sum(post.live_post_ids.mapped('comments_count')),
'shares_count': sum(post.live_post_ids.mapped('shares_count')),
})
except Exception as e:
post.write({
'state': 'failed',
'error_message': f"Yayınlama Hatası: {str(e)}"
})
def action_reset_to_draft(self):
self.write({'state': 'draft', 'error_message': False})
@api.model
def _cron_publish_scheduled_posts(self):
_logger.info("Cron: Zamanlanmış gönderiler kontrol ediliyor...")
posts_to_publish = self.search([
('state', '=', 'scheduled'),
('scheduled_date', '<=', fields.Datetime.now())
])
if posts_to_publish:
_logger.info(f"Cron: Yayınlanacak {len(posts_to_publish)} gönderi bulundu.")
posts_to_publish.action_publish()

View File

@@ -0,0 +1,17 @@
from odoo import models, fields, api
class SocialStream(models.Model):
_name = 'mymach.social.stream'
_description = 'Social Media Stream'
name = fields.Char(string='Akış Adı', required=True)
account_id = fields.Many2one('mymach.social.account', string='Hesap', required=True, ondelete='cascade')
stream_post_ids = fields.One2many('mymach.social.stream.post', 'stream_id', string='Akış Gönderileri')
stream_type = fields.Selection([
('feed', 'Zaman Tüneli / Feed'),
('mentions', 'Bahsetmeler'),
('messages', 'Mesajlar'),
('keyword', 'Anahtar Kelime (Dinleme)')
], string='Akış Tipi', default='feed', required=True)
search_keyword = fields.Char(string='Takip Edilecek Kelime/Hashtag', help='Sadece Akış Tipi "Anahtar Kelime" ise kullanılır. Örn: #teknoloji')

View File

@@ -0,0 +1,140 @@
from odoo import models, fields, api
class SocialStreamPost(models.Model):
_name = 'mymach.social.stream.post'
_inherit = ['mail.thread']
_description = 'Social Stream Post'
_order = 'published_date desc'
stream_id = fields.Many2one('mymach.social.stream', string='Akış', required=True, ondelete='cascade')
account_id = fields.Many2one(related='stream_id.account_id', string='Hesap', store=True)
author_name = fields.Char(string='Yazar')
author_link = fields.Char(string='Yazar Profil Linki')
published_date = fields.Datetime(string='Yayın Tarihi')
message = fields.Text(string='Mesaj / İçerik')
link_url = fields.Char(string='Gönderi Linki')
likes_count = fields.Integer(string='Beğeniler')
comments_count = fields.Integer(string='Yorumlar')
shares_count = fields.Integer(string='Paylaşımlar')
platform_post_id = fields.Char(string='Platform ID')
sentiment = fields.Selection([
('positive', '😊 Olumlu'),
('neutral', '😐 Nötr'),
('negative', '😡 Olumsuz')
], string='Duygu Analizi', default='neutral')
message_type = fields.Selection([
('comment', 'Yorum'),
('direct_message', 'Özel Mesaj (DM)')
], string='Mesaj Tipi', default='comment', required=True)
@api.model_create_multi
def create(self, vals_list):
records = super().create(vals_list)
for record in records:
if not record.message:
continue
# Otomatik Yanıt Kurallarını Kontrol Et
rules = self.env['mymach.social.auto.reply'].search([('active', '=', True)], order='sequence, id')
for rule in rules:
# Hesap Filtresi Kontrolü
if rule.account_ids and record.account_id not in rule.account_ids:
continue
# Mesaj Tipi Filtresi Kontrolü
if rule.apply_to_message_type != 'all' and rule.apply_to_message_type != record.message_type:
continue
# Kelime Kontrolü
if rule.keyword.lower() in record.message.lower():
# Yanıtı gönder
record.message_post(body=f"🤖 <b>Otomatik Yanıt Gönderildi:</b><br/>'{rule.keyword}' kelimesi algılandı.<br/><b>Yanıt:</b> {rule.reply_message}")
# Log kaydını oluştur
self.env['mymach.social.auto.reply.log'].create({
'rule_id': rule.id,
'post_id': record.id,
'account_id': record.account_id.id,
'author_name': record.author_name,
'trigger_keyword': rule.keyword,
'sent_message': rule.reply_message,
'status': 'success'
})
break # İlk eşleşen kural çalışsın ve döngü bitsin.
return records
def action_analyze_sentiment(self):
# API anahtarını al
api_key = self.env['ir.config_parameter'].sudo().get_param('mymach_social.gemini_api_key')
for post in self:
if not post.message:
continue
if api_key:
# Gerçek AI Analizi (Gemini)
try:
import requests
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={api_key}"
system_prompt = "Sen bir duygu analizi uzmanısın. Verilen metnin duygusunu analiz et ve sadece şu üç kelimeden birini döndür: 'positive', 'neutral', 'negative'."
payload = {
"system_instruction": {"parts": [{"text": system_prompt}]},
"contents": [{"parts": [{"text": f"Metin: {post.message}"}]}],
"generationConfig": {"temperature": 0.1, "maxOutputTokens": 10}
}
response = requests.post(url, headers={'Content-Type': 'application/json'}, json=payload)
if response.status_code == 200:
result = response.json().get('candidates', [{}])[0].get('content', {}).get('parts', [{}])[0].get('text', '').strip().lower()
if 'positive' in result:
post.sentiment = 'positive'
elif 'negative' in result:
post.sentiment = 'negative'
else:
post.sentiment = 'neutral'
continue
except Exception as e:
# Log error, fallback to basic logic
pass
# Fallback (API Key yoksa veya hata olursa) Basit kelime analizi
lower_msg = post.message.lower()
if any(word in lower_msg for word in ['harika', 'süper', 'teşekkür', 'iyi', 'başarılı']):
post.sentiment = 'positive'
elif any(word in lower_msg for word in ['kötü', 'rezalet', 'şikayet', 'sorun', 'hata', 'berbat']):
post.sentiment = 'negative'
else:
post.sentiment = 'neutral'
def action_suggest_reply(self):
self.ensure_one()
return {
'name': 'AI ile Yanıt Öner',
'type': 'ir.actions.act_window',
'res_model': 'mymach.social.ai.reply.wizard',
'view_mode': 'form',
'target': 'new',
'context': {'default_stream_post_id': self.id}
}
def action_create_lead(self):
self.ensure_one()
# Odoo CRM'de yeni bir Lead (Fırsat/Müşteri Adayı) yarat
lead = self.env['crm.lead'].create({
'name': f"Sosyal Medya Fırsatı: {self.author_name}",
'description': f"İçerik: {self.message}\nLink: {self.link_url}",
'type': 'opportunity'
})
return {
'name': 'Yeni Müşteri Adayı',
'type': 'ir.actions.act_window',
'res_model': 'crm.lead',
'res_id': lead.id,
'view_mode': 'form',
'target': 'current'
}

View File

@@ -1,9 +1,14 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_social_account_user,social.account.user,model_social_account,base.group_user,1,1,1,0
access_social_account_manager,social.account.manager,model_social_account,base.group_system,1,1,1,1
access_social_post_user,social.post.user,model_social_post,base.group_user,1,1,1,0
access_social_post_manager,social.post.manager,model_social_post,base.group_system,1,1,1,1
access_social_media_user,social.media.user,model_social_media,base.group_user,1,1,1,0
access_social_media_manager,social.media.manager,model_social_media,base.group_system,1,1,1,1
access_social_competitor_user,social.competitor.user,model_social_competitor,base.group_user,1,1,1,0
access_social_competitor_manager,social.competitor.manager,model_social_competitor,base.group_system,1,1,1,1
access_mymach_social_account_user,mymach.social.account.user,model_mymach_social_account,base.group_user,1,1,1,1
access_mymach_social_post_user,mymach.social.post.user,model_mymach_social_post,base.group_user,1,1,1,1
access_mymach_social_ai_wizard_user,mymach.social.ai.wizard.user,model_mymach_social_ai_wizard,base.group_user,1,1,1,1
access_mymach_social_live_post_user,mymach.social.live.post.user,model_mymach_social_live_post,base.group_user,1,1,1,1
access_mymach_social_stream_user,mymach.social.stream.user,model_mymach_social_stream,base.group_user,1,1,1,1
access_mymach_social_stream_post_user,mymach.social.stream.post.user,model_mymach_social_stream_post,base.group_user,1,1,1,1
access_mymach_social_competitor_user,social.competitor.user,model_social_competitor,base.group_user,1,1,1,1
access_mymach_social_ai_image_wizard_user,mymach.social.ai.image.wizard.user,model_mymach_social_ai_image_wizard,base.group_user,1,1,1,1
access_mymach_social_ai_reply_wizard_user,mymach.social.ai.reply.wizard.user,model_mymach_social_ai_reply_wizard,base.group_user,1,1,1,1
access_social_media,access.mymach.social.media,model_mymach_social_media,base.group_user,1,1,1,1
access_social_media_tag,access.mymach.social.media.tag,model_mymach_social_media_tag,base.group_user,1,1,1,1
access_social_auto_reply,access.mymach.social.auto.reply,model_mymach_social_auto_reply,base.group_user,1,1,1,1
access_social_auto_reply_log,access.mymach.social.auto.reply.log,model_mymach_social_auto_reply_log,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_social_account_user access_mymach_social_account_user social.account.user mymach.social.account.user model_social_account model_mymach_social_account base.group_user 1 1 1 0 1
3 access_social_account_manager access_mymach_social_post_user social.account.manager mymach.social.post.user model_social_account model_mymach_social_post base.group_system base.group_user 1 1 1 1
4 access_social_post_user access_mymach_social_ai_wizard_user social.post.user mymach.social.ai.wizard.user model_social_post model_mymach_social_ai_wizard base.group_user 1 1 1 0 1
5 access_social_post_manager access_mymach_social_live_post_user social.post.manager mymach.social.live.post.user model_social_post model_mymach_social_live_post base.group_system base.group_user 1 1 1 1
6 access_social_media_user access_mymach_social_stream_user social.media.user mymach.social.stream.user model_social_media model_mymach_social_stream base.group_user 1 1 1 0 1
7 access_social_media_manager access_mymach_social_stream_post_user social.media.manager mymach.social.stream.post.user model_social_media model_mymach_social_stream_post base.group_system base.group_user 1 1 1 1
8 access_social_competitor_user access_mymach_social_competitor_user social.competitor.user model_social_competitor base.group_user 1 1 1 0 1
9 access_social_competitor_manager access_mymach_social_ai_image_wizard_user social.competitor.manager mymach.social.ai.image.wizard.user model_social_competitor model_mymach_social_ai_image_wizard base.group_system base.group_user 1 1 1 1
10 access_mymach_social_ai_reply_wizard_user mymach.social.ai.reply.wizard.user model_mymach_social_ai_reply_wizard base.group_user 1 1 1 1
11 access_social_media access.mymach.social.media model_mymach_social_media base.group_user 1 1 1 1
12 access_social_media_tag access.mymach.social.media.tag model_mymach_social_media_tag base.group_user 1 1 1 1
13 access_social_auto_reply access.mymach.social.auto.reply model_mymach_social_auto_reply base.group_user 1 1 1 1
14 access_social_auto_reply_log access.mymach.social.auto.reply.log model_mymach_social_auto_reply_log base.group_user 1 1 1 1

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!--
Account Modeli İçin Kurallar
-->
<record id="rule_social_account_user" model="ir.rule">
<field name="name">Social Account: User Rule</field>
<field name="model_id" ref="model_mymach_social_account"/>
<field name="domain_force">[('create_uid', '=', user.id)]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
</record>
<record id="rule_social_account_manager" model="ir.rule">
<field name="name">Social Account: Manager Rule</field>
<field name="model_id" ref="model_mymach_social_account"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('base.group_system'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
</record>
<!--
Post Modeli İçin Kurallar
-->
<record id="rule_social_post_user" model="ir.rule">
<field name="name">Social Post: User Rule</field>
<field name="model_id" ref="model_mymach_social_post"/>
<field name="domain_force">[('create_uid', '=', user.id)]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
</record>
<record id="rule_social_post_manager" model="ir.rule">
<field name="name">Social Post: Manager Rule</field>
<field name="model_id" ref="model_mymach_social_post"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('base.group_system'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Sosyal Medya Uzmanı (Normal Kullanıcı) -->
<record id="group_social_user" model="res.groups">
<field name="name">Sosyal Medya Uzmanı</field>
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
<field name="comment">Gönderi taslağı hazırlayabilir ve onay bekler statüsüne alabilir.</field>
</record>
<!-- Sosyal Medya Yöneticisi (Manager) -->
<record id="group_social_manager" model="res.groups">
<field name="name">Sosyal Medya Yöneticisi</field>
<field name="implied_ids" eval="[(4, ref('group_social_user'))]"/>
<field name="comment">Taslakları onaylayabilir, doğrudan yayınlayabilir ve gelişmiş ayarları görebilir.</field>
<field name="user_ids" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,137 @@
/* mymach_social_nextgen/static/src/css/social_dashboard.css */
/* 1. KANBAN VIEW (ANA ARKA PLAN) */
.o_kanban_view.o_social_dashboard_kanban {
background-color: #020617 !important; /* Çok daha koyu bir arka plan (Slate-950) */
padding: 16px !important;
min-height: 100vh !important;
}
/* 2. SÜTUNLAR (STREAMS) */
.o_social_dashboard_kanban .o_kanban_group {
background-color: transparent !important;
border: none !important;
padding: 0 8px !important;
}
/* Sütun Başlığı (Header) */
.o_social_dashboard_kanban .o_kanban_header {
background-color: #334155 !important; /* Arka plana göre çok daha açık bir renk (Slate-700) */
border: 1px solid #475569 !important;
border-radius: 12px 12px 0 0 !important;
padding: 16px !important;
border-bottom: none !important;
display: flex;
align-items: center;
}
/* Sütun Başlığı Metni */
.o_social_dashboard_kanban .o_kanban_header .o_column_title,
.o_social_dashboard_kanban .o_kanban_header span,
.o_social_dashboard_kanban .o_kanban_header div {
color: #ffffff !important;
font-weight: 700 !important;
font-size: 16px !important;
letter-spacing: 0.3px;
text-shadow: 0 1px 2px rgba(0,0,0,0.5);
}
/* Sütun İçeriği (Kartların dizildiği yer) */
.o_social_dashboard_kanban .o_kanban_record_has_image_fill,
.o_social_dashboard_kanban .o_kanban_group > .o_kanban_record {
background: transparent !important;
}
/* 3. KARTLAR (POSTS) */
.o_social_dashboard_kanban .o_kanban_record {
background-color: #1e293b !important; /* Başlıkla aynı uyumlu renk */
border: 1px solid #334155 !important;
border-radius: 12px !important;
margin-top: 12px !important;
margin-bottom: 12px !important;
color: #e2e8f0 !important;
padding: 16px !important;
transition: all 0.2s ease-in-out;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important;
display: flex;
flex-direction: column;
}
/* Kart Hover Efekti */
.o_social_dashboard_kanban .o_kanban_record:hover {
border-color: #64748b !important;
transform: translateY(-3px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.1) !important;
}
/* Yazar Bilgisi Alanı */
.o_social_author_section {
display: flex;
align-items: center;
margin-bottom: 16px;
}
/* Yazar Avatarı */
.o_social_author_avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background: linear-gradient(135deg, #3b82f6, #8b5cf6); /* Şık bir gradient */
margin-right: 12px;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.o_social_author_name {
font-weight: 600;
color: #f1f5f9;
font-size: 14px;
}
.o_social_time {
color: #94a3b8;
font-size: 12px;
margin-top: 2px;
}
/* Post İçeriği (Mesaj) */
.o_social_message_text {
font-size: 14px;
line-height: 1.6;
color: #cbd5e1;
margin-bottom: 20px;
white-space: pre-wrap;
word-break: break-word;
flex-grow: 1; /* Mesaj kısmının esnek olması için */
}
/* Alt Aksiyon Butonları (Beğeni, Yorum, Paylaş) */
.o_social_actions {
display: flex;
gap: 20px;
color: #94a3b8;
font-size: 14px;
border-top: 1px solid #334155;
padding-top: 14px;
margin-top: auto;
}
.o_social_actions div {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
transition: color 0.2s;
}
.o_social_actions div:hover {
color: #38bdf8; /* Hover durumunda tatlı bir mavi */
}
.o_social_actions i {
font-size: 16px;
}
/* Odoo'nun Default Ekleme/Yükleme Butonlarını Gizle (Gerekirse) */
.o_social_dashboard_kanban .o_kanban_group .o_kanban_header .o_kanban_config {
color: #94a3b8 !important;
}

View File

@@ -0,0 +1,2 @@
from . import test_social_core
from . import test_social_auto_reply

View File

@@ -0,0 +1,85 @@
from odoo.tests.common import TransactionCase
class TestSocialAutoReply(TransactionCase):
@classmethod
def setUpClass(cls):
super(TestSocialAutoReply, cls).setUpClass()
# Test izolasyonu için mevcut tüm kuralları pasif yap
cls.env['mymach.social.auto.reply'].search([]).write({'active': False})
# Testler için gerekli verileri oluştur
cls.social_account = cls.env['mymach.social.account'].create({
'name': 'Test Account',
'platform': 'facebook',
'active': True,
})
cls.stream = cls.env['mymach.social.stream'].create({
'name': 'Test Akışı',
'account_id': cls.social_account.id,
'stream_type': 'feed'
})
def test_01_auto_reply_rule_creation(self):
"""Otomatik yanıt kuralı oluşturulabildiğini test et."""
rule = self.env['mymach.social.auto.reply'].create({
'name': 'Fiyat Kuralı',
'keyword': 'fiyat',
'reply_message': 'Fiyatlarımız için sitemize bakınız.',
'active': True
})
self.assertEqual(rule.keyword, 'fiyat')
self.assertEqual(rule.log_count, 0)
def test_02_auto_reply_trigger_and_log(self):
"""Bir gönderi oluşturulduğunda kuralın tetiklenip log oluşturduğunu test et."""
# 1. Kuralı oluştur
rule = self.env['mymach.social.auto.reply'].create({
'name': 'Fiyat Kuralı',
'keyword': 'fiyat',
'reply_message': 'Fiyatlarımız web sitemizdedir.',
'active': True
})
# Başlangıçta log yok
self.assertEqual(rule.log_count, 0)
# 2. 'fiyat' kelimesini içeren bir Stream Post oluştur
# Oluşturma işlemi (create) sırasında `mymach.social.stream.post` kuralı kontrol edip log atacak
post = self.env['mymach.social.stream.post'].create({
'stream_id': self.stream.id,
'author_name': 'Müşteri',
'message': 'Merhaba, ürünün fiyat listesini alabilir miyim?',
'message_type': 'comment'
})
# 3. Logun oluştuğunu doğrula
log = self.env['mymach.social.auto.reply.log'].search([('rule_id', '=', rule.id)])
self.assertEqual(len(log), 1)
rule.invalidate_recordset()
self.assertEqual(rule.log_count, 1)
self.assertEqual(log.status, 'success')
self.assertEqual(log.post_id.id, post.id)
self.assertEqual(log.trigger_keyword, 'fiyat')
def test_03_no_auto_reply_if_no_keyword(self):
"""Kural kelimesi eşleşmediğinde log oluşmadığını test et."""
rule = self.env['mymach.social.auto.reply'].create({
'name': 'Şikayet Kuralı',
'keyword': 'şikayet',
'reply_message': 'Lütfen destek hattını arayın.',
'active': True
})
# 'fiyat' geçen ama 'şikayet' geçmeyen mesaj
self.env['mymach.social.stream.post'].create({
'stream_id': self.stream.id,
'author_name': 'Müşteri 2',
'message': 'Sadece fiyat sormuştum.',
'message_type': 'comment'
})
self.assertEqual(rule.log_count, 0)

View File

@@ -0,0 +1,43 @@
from odoo.tests.common import TransactionCase
from odoo.exceptions import ValidationError
class TestSocialCore(TransactionCase):
@classmethod
def setUpClass(cls):
super(TestSocialCore, cls).setUpClass()
# Test hesabı oluştur
cls.social_account = cls.env['mymach.social.account'].create({
'name': 'Test Account',
'platform': 'facebook',
'active': True,
})
def test_01_social_account_creation(self):
"""Sosyal hesabın doğru ilişkilerle oluşturulduğunu test et."""
self.assertEqual(self.social_account.platform, 'facebook')
self.assertTrue(self.social_account.active)
def test_02_social_post_creation(self):
"""Sosyal gönderi oluşturma ve durum (state) doğrulama."""
post = self.env['mymach.social.post'].create({
'name': 'Test Gönderisi',
'content': 'Bu bir test gönderisidir.',
'account_ids': [(4, self.social_account.id)]
})
self.assertEqual(post.state, 'draft')
self.assertIn(self.social_account, post.account_ids)
# Post the message
post.action_publish()
self.assertEqual(post.state, 'published')
def test_03_social_stream_creation(self):
"""Sosyal akış (stream) oluşturmayı test et."""
stream = self.env['mymach.social.stream'].create({
'name': 'Test Akışı',
'account_id': self.social_account.id,
'stream_type': 'feed'
})
self.assertEqual(stream.name, 'Test Akışı')
self.assertEqual(stream.account_id.name, 'Test Account')

View File

@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.social</field>
<field name="model">res.config.settings</field>
<field name="priority" eval="90"/>
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//form" position="inside">
<app data-string="MyMach Social" string="MyMach Social" name="mymach_social_nextgen" groups="base.group_system">
<block title="Sosyal Medya API Kimlik Bilgileri" name="social_api_credentials_setting_container">
<setting string="Meta (Facebook/Instagram)" help="Meta Geliştirici Uygulaması için Client ID ve Secret">
<div class="content-group">
<div class="row mt16">
<label for="social_meta_client_id" class="col-lg-3 o_light_label"/>
<field name="social_meta_client_id" class="oe_inline"/>
</div>
<div class="row mt8">
<label for="social_meta_client_secret" class="col-lg-3 o_light_label"/>
<field name="social_meta_client_secret" class="oe_inline" password="True"/>
</div>
</div>
</setting>
<setting string="LinkedIn" help="LinkedIn Geliştirici Uygulaması için Client ID ve Secret">
<div class="content-group">
<div class="row mt16">
<label for="social_linkedin_client_id" class="col-lg-3 o_light_label"/>
<field name="social_linkedin_client_id" class="oe_inline"/>
</div>
<div class="row mt8">
<label for="social_linkedin_client_secret" class="col-lg-3 o_light_label"/>
<field name="social_linkedin_client_secret" class="oe_inline" password="True"/>
</div>
</div>
</setting>
<setting string="X (Twitter)" help="X Geliştirici Portalı için Client ID ve Secret">
<div class="content-group">
<div class="row mt16">
<label for="social_x_client_id" class="col-lg-3 o_light_label"/>
<field name="social_x_client_id" class="oe_inline"/>
</div>
<div class="row mt8">
<label for="social_x_client_secret" class="col-lg-3 o_light_label"/>
<field name="social_x_client_secret" class="oe_inline" password="True"/>
</div>
</div>
</setting>
<setting string="YouTube" help="Google Cloud Console için Client ID ve Secret">
<div class="content-group">
<div class="row mt16">
<label for="social_youtube_client_id" class="col-lg-3 o_light_label"/>
<field name="social_youtube_client_id" class="oe_inline"/>
</div>
<div class="row mt8">
<label for="social_youtube_client_secret" class="col-lg-3 o_light_label"/>
<field name="social_youtube_client_secret" class="oe_inline" password="True"/>
</div>
</div>
</setting>
</block>
<block title="Yapay Zeka Asistan Ayarları" name="social_ai_settings_container">
<setting string="Google Gemini" help="Gemini Pro Entegrasyonu için API Anahtarı">
<div class="content-group">
<div class="row mt16">
<label for="social_gemini_api_key" class="col-lg-3 o_light_label"/>
<field name="social_gemini_api_key" class="oe_inline" password="True"/>
</div>
</div>
</setting>
<setting string="OpenAI (ChatGPT)" help="OpenAI modelleri için API Anahtarı">
<div class="content-group">
<div class="row mt16">
<label for="social_openai_api_key" class="col-lg-3 o_light_label"/>
<field name="social_openai_api_key" class="oe_inline" password="True"/>
</div>
</div>
</setting>
<setting string="Anthropic (Claude)" help="Claude AI modelleri için API Anahtarı">
<div class="content-group">
<div class="row mt16">
<label for="social_claude_api_key" class="col-lg-3 o_light_label"/>
<field name="social_claude_api_key" class="oe_inline" password="True"/>
</div>
</div>
</setting>
<setting string="xAI (Grok)" help="Grok modelleri için API Anahtarı">
<div class="content-group">
<div class="row mt16">
<label for="social_grok_api_key" class="col-lg-3 o_light_label"/>
<field name="social_grok_api_key" class="oe_inline" password="True"/>
</div>
</div>
</setting>
</block>
<block title="Medya Kütüphanesi &amp; Filigran (Watermark) Ayarları" name="social_watermark_settings_container">
<setting string="Şirket Logosu (Filigran)" help="Üretilen veya yüklenen medyaların üzerine otomatik basılacak PNG formatında şeffaf logo.">
<div class="content-group">
<div class="row mt16">
<label for="social_watermark_image" class="col-lg-3 o_light_label"/>
<field name="social_watermark_image" widget="image" class="oe_avatar"/>
</div>
<div class="row mt8">
<label for="social_watermark_position" class="col-lg-3 o_light_label"/>
<field name="social_watermark_position" class="oe_inline"/>
</div>
</div>
</setting>
</block>
</app>
</xpath>
</field>
</record>
<!-- Action for Settings -->
<record id="action_social_config_settings" model="ir.actions.act_window">
<field name="name">Ayarlar</field>
<field name="res_model">res.config.settings</field>
<field name="view_id" ref="res_config_settings_view_form"/>
<field name="view_mode">form</field>
<field name="context">{'module' : 'mymach_social_nextgen', 'bin_size': False}</field>
</record>
<menuitem id="menu_social_settings"
name="Ayarlar"
parent="menu_social_configuration"
action="action_social_config_settings"
sequence="100"/>
</data>
</odoo>

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Tree View -->
<record id="view_social_account_tree" model="ir.ui.view">
<field name="name">mymach.social.account.list</field>
<field name="model">mymach.social.account</field>
<field name="arch" type="xml">
<list string="Social Accounts">
<field name="name"/>
<field name="platform"/>
<field name="active"/>
</list>
</field>
</record>
<!-- Form View -->
<record id="view_social_account_form" model="ir.ui.view">
<field name="name">mymach.social.account.form</field>
<field name="model">mymach.social.account</field>
<field name="arch" type="xml">
<form string="Sosyal Medya Hesabı">
<header>
<button name="action_authenticate" string="Platforma Bağlan" type="object" class="oe_highlight" invisible="access_token != False"/>
<button name="action_refresh_token" string="Token Yenile" type="object" invisible="access_token == False"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_authenticate" type="object" class="oe_stat_button" icon="fa-users">
<field name="audience_count" widget="statinfo" string="Takipçiler"/>
</button>
<button name="action_authenticate" type="object" class="oe_stat_button" icon="fa-line-chart">
<field name="engagement_rate" widget="statinfo" string="Etkileşim %"/>
</button>
</div>
<div class="oe_title">
<h1><field name="name" placeholder="Hesap Adı..."/></h1>
</div>
<group>
<group>
<field name="platform"/>
<field name="active"/>
</group>
<group>
<field name="platform_account_id" invisible="platform_account_id == False"/>
<field name="token_expiration" invisible="access_token == False"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<!-- Action -->
<record id="action_social_account" model="ir.actions.act_window">
<field name="name">Sosyal Hesaplar</field>
<field name="res_model">mymach.social.account</field>
<field name="view_mode">list,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create your first Social Media Account connection!
</p>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Graph View -->
<record id="view_social_post_graph" model="ir.ui.view">
<field name="name">mymach.social.post.graph</field>
<field name="model">mymach.social.post</field>
<field name="arch" type="xml">
<graph string="Sosyal Medya Analizleri" type="bar" sample="1">
<field name="scheduled_date" interval="month"/>
<field name="state"/>
<field name="likes_count" type="measure"/>
</graph>
</field>
</record>
<!-- Pivot View for Analytics -->
<record id="view_social_post_pivot" model="ir.ui.view">
<field name="name">mymach.social.post.pivot</field>
<field name="model">mymach.social.post</field>
<field name="arch" type="xml">
<pivot string="Analiz Tablosu" sample="1">
<field name="scheduled_date" type="row" interval="month"/>
<field name="state" type="col"/>
<field name="likes_count" type="measure"/>
<field name="comments_count" type="measure"/>
<field name="shares_count" type="measure"/>
</pivot>
</field>
</record>
<!-- Sentiment Analysis Pie Chart -->
<record id="view_social_sentiment_graph" model="ir.ui.view">
<field name="name">mymach.social.sentiment.graph</field>
<field name="model">mymach.social.stream.post</field>
<field name="arch" type="xml">
<graph string="Duygu Analizi" type="pie" sample="1">
<field name="sentiment"/>
</graph>
</field>
</record>
<record id="action_social_sentiment_dashboard" model="ir.actions.act_window">
<field name="name">Duygu Analizi Panosu</field>
<field name="res_model">mymach.social.stream.post</field>
<field name="view_mode">graph,list,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Müşteri Geri Bildirimleri Analizi
</p>
</field>
</record>
<!-- Dashboard Action -->
<record id="action_social_dashboard" model="ir.actions.act_window">
<field name="name">Analiz Dashboard</field>
<field name="res_model">mymach.social.post</field>
<field name="view_mode">graph,pivot,kanban,list</field>
<field name="context">{'search_default_state': 'published'}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Sosyal Medya Performansınızı Takip Edin!
</p>
<p>
Gönderilerinizi yayınladıkça etkileşim grafikleriniz burada belirecektir.
</p>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Log List (Tree) View -->
<record id="view_social_auto_reply_log_tree" model="ir.ui.view">
<field name="name">mymach.social.auto.reply.log.tree</field>
<field name="model">mymach.social.auto.reply.log</field>
<field name="arch" type="xml">
<list string="Otomatik Yanıt Geçmişi" decoration-success="status == 'success'" decoration-danger="status == 'failed'">
<field name="date" widget="datetime"/>
<field name="rule_id"/>
<field name="author_name"/>
<field name="account_id"/>
<field name="trigger_keyword"/>
<field name="status" widget="badge" decoration-success="status == 'success'" decoration-danger="status == 'failed'"/>
</list>
</field>
</record>
<!-- Log Form View -->
<record id="view_social_auto_reply_log_form" model="ir.ui.view">
<field name="name">mymach.social.auto.reply.log.form</field>
<field name="model">mymach.social.auto.reply.log</field>
<field name="arch" type="xml">
<form string="Otomatik Yanıt İşlemi">
<header>
<field name="status" widget="statusbar" statusbar_visible="success,failed"/>
</header>
<sheet>
<group>
<group>
<field name="rule_id" readonly="1"/>
<field name="date" readonly="1"/>
<field name="account_id" readonly="1"/>
</group>
<group>
<field name="author_name" readonly="1"/>
<field name="trigger_keyword" readonly="1"/>
<field name="post_id" readonly="1"/>
</group>
</group>
<notebook>
<page string="Gönderilen Yanıt">
<field name="sent_message" readonly="1"/>
</page>
<page string="Hata Mesajı" invisible="status == 'success'">
<field name="error_message" readonly="1"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- Search View -->
<record id="view_social_auto_reply_log_search" model="ir.ui.view">
<field name="name">mymach.social.auto.reply.log.search</field>
<field name="model">mymach.social.auto.reply.log</field>
<field name="arch" type="xml">
<search>
<field name="rule_id"/>
<field name="author_name"/>
<field name="account_id"/>
<separator/>
<filter string="Başarılı" name="success" domain="[('status', '=', 'success')]"/>
<filter string="Başarısız" name="failed" domain="[('status', '=', 'failed')]"/>
<separator/>
<filter string="Kurala Göre Grupla" name="group_rule" context="{'group_by':'rule_id'}"/>
<filter string="Tarihe Göre Grupla" name="group_date" context="{'group_by':'date:day'}"/>
</search>
</field>
</record>
<!-- Action -->
<record id="action_social_auto_reply_log" model="ir.actions.act_window">
<field name="name">İşlem Geçmişi</field>
<field name="res_model">mymach.social.auto.reply.log</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_social_auto_reply_log_search"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Form View -->
<record id="view_social_auto_reply_form" model="ir.ui.view">
<field name="name">mymach.social.auto.reply.form</field>
<field name="model">mymach.social.auto.reply</field>
<field name="arch" type="xml">
<form string="Otomatik Yanıt Kuralı">
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_view_logs" type="object" class="oe_stat_button" icon="fa-history" invisible="log_count == 0">
<field name="log_count" widget="statinfo" string="İşlemler"/>
</button>
</div>
<widget name="web_ribbon" title="Pasif" bg_color="bg-danger" invisible="active == True"/>
<div class="oe_title">
<h1>
<field name="name" placeholder="Kural Adı (Örn: Bilgi Talebi)"/>
</h1>
</div>
<group>
<group string="Kural Ayarları">
<field name="keyword" placeholder="bilgi"/>
<field name="active" widget="boolean_toggle"/>
<field name="sequence"/>
</group>
<group string="Filtreler">
<field name="apply_to_message_type" widget="radio" options="{'horizontal': true}"/>
<field name="account_ids" widget="many2many_tags" placeholder="Tüm hesaplar"/>
</group>
</group>
<group string="Yanıt İçeriği">
<field name="reply_message" nolabel="1" widget="text" placeholder="Gelecek olan mesajda bu kelime geçiyorsa otomatik olarak şu metni gönder..."/>
</group>
</sheet>
</form>
</field>
</record>
<!-- List View -->
<record id="view_social_auto_reply_list" model="ir.ui.view">
<field name="name">mymach.social.auto.reply.list</field>
<field name="model">mymach.social.auto.reply</field>
<field name="arch" type="xml">
<list string="Otomatik Yanıt Kuralları">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="keyword"/>
<field name="active" widget="boolean_toggle"/>
</list>
</field>
</record>
<!-- Action -->
<record id="action_social_auto_reply" model="ir.actions.act_window">
<field name="name">Otomatik Yanıt Kuralları</field>
<field name="res_model">mymach.social.auto.reply</field>
<field name="view_mode">list,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
İlk Otomatik Yanıt Kuralınızı Oluşturun!
</p>
<p>
Gelen yorum veya mesajlarda spesifik anahtar kelimeler ("bilgi", "fiyat" vb.) geçtiğinde sistemin otomatik yanıt vermesini sağlayabilirsiniz.
</p>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,121 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- List View -->
<record id="view_social_competitor_list" model="ir.ui.view">
<field name="name">social.competitor.list</field>
<field name="model">social.competitor</field>
<field name="arch" type="xml">
<list string="Rakipler">
<field name="name"/>
<field name="platform"/>
<field name="followers_count"/>
<field name="engagement_rate"/>
<field name="last_scraped"/>
</list>
</field>
</record>
<!-- Form View -->
<record id="view_social_competitor_form" model="ir.ui.view">
<field name="name">social.competitor.form</field>
<field name="model">social.competitor</field>
<field name="arch" type="xml">
<form string="Rakip Bilgisi">
<header>
<button name="action_scrape_data" string="Verileri Kazı (Güncelle)" type="object" class="oe_highlight"/>
</header>
<sheet>
<div class="oe_title">
<h1>
<field name="name" placeholder="Rakip Firma Adı"/>
</h1>
</div>
<group>
<group>
<field name="platform"/>
<field name="profile_url" widget="url"/>
</group>
<group>
<field name="followers_count"/>
<field name="engagement_rate"/>
<field name="last_scraped"/>
</group>
</group>
<notebook>
<page string="Analiz Notları">
<field name="notes" placeholder="Stratejik notlar..."/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- Kanban View -->
<record id="view_social_competitor_kanban" model="ir.ui.view">
<field name="name">social.competitor.kanban</field>
<field name="model">social.competitor</field>
<field name="arch" type="xml">
<kanban class="o_kanban_dashboard">
<field name="name"/>
<field name="platform"/>
<field name="followers_count"/>
<field name="engagement_rate"/>
<template>
<t t-name="card">
<div class="oe_kanban_global_click">
<div class="o_kanban_record_title">
<strong><field name="name"/></strong>
</div>
<div class="mt-2">
<span class="badge rounded-pill text-bg-primary"><field name="platform"/></span>
</div>
<div class="mt-2">
<i class="fa fa-users" title="Takipçiler"/> <field name="followers_count"/>
</div>
<div class="mt-1">
<i class="fa fa-line-chart" title="Etkileşim Oranı"/> <field name="engagement_rate"/> %
</div>
</div>
</t>
</template>
</kanban>
</field>
</record>
<!-- Graph View -->
<record id="view_social_competitor_graph" model="ir.ui.view">
<field name="name">social.competitor.graph</field>
<field name="model">social.competitor</field>
<field name="arch" type="xml">
<graph string="Rakip Takipçi Karşılaştırması" type="bar">
<field name="name"/>
<field name="followers_count" type="measure"/>
</graph>
</field>
</record>
<!-- Action -->
<record id="action_social_competitor" model="ir.actions.act_window">
<field name="name">Rakipler</field>
<field name="res_model">social.competitor</field>
<field name="view_mode">kanban,list,graph,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
İlk Rakibinizi Ekleyin!
</p>
<p>
Rakiplerinizin sosyal medya hesaplarını ekleyerek durumlarını izleyin.
</p>
</field>
</record>
<!-- Menu Item -->
<menuitem id="menu_social_competitors"
name="Rakipler"
parent="mymach_social_nextgen.menu_social_root"
action="action_social_competitor"
sequence="35"/>
</data>
</odoo>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_social_live_post_list" model="ir.ui.view">
<field name="name">mymach.social.live.post.list</field>
<field name="model">mymach.social.live.post</field>
<field name="arch" type="xml">
<list string="Canlı Gönderiler" decoration-info="state == 'ready'" decoration-success="state == 'posted'" decoration-danger="state == 'failed'">
<field name="account_id"/>
<field name="post_id"/>
<field name="state" widget="badge"/>
<field name="likes_count"/>
<field name="comments_count"/>
<field name="shares_count"/>
</list>
</field>
</record>
<!-- Action -->
<record id="action_social_live_post" model="ir.actions.act_window">
<field name="name">Canlı Gönderiler</field>
<field name="res_model">mymach.social.live.post</field>
<field name="view_mode">list,form</field>
<field name="domain">[('post_id', '=', active_id)]</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Media Form View -->
<record id="view_social_media_form" model="ir.ui.view">
<field name="name">mymach.social.media.form</field>
<field name="model">mymach.social.media</field>
<field name="arch" type="xml">
<form string="Medya Kütüphanesi">
<sheet>
<div class="oe_title">
<h1>
<field name="name" placeholder="Medya Adı..."/>
</h1>
</div>
<group>
<group>
<field name="media_type"/>
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
</group>
<group>
<field name="attachment_id"/>
<field name="image_preview" widget="image" class="oe_avatar" invisible="media_type != 'image'"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<!-- Media List View -->
<record id="view_social_media_list" model="ir.ui.view">
<field name="name">mymach.social.media.list</field>
<field name="model">mymach.social.media</field>
<field name="arch" type="xml">
<list string="Medya Kütüphanesi">
<field name="name"/>
<field name="media_type"/>
<field name="tag_ids" widget="many2many_tags"/>
</list>
</field>
</record>
<!-- Media Kanban View -->
<record id="view_social_media_kanban" model="ir.ui.view">
<field name="name">mymach.social.media.kanban</field>
<field name="model">mymach.social.media</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile">
<field name="name"/>
<field name="media_type"/>
<field name="image_preview"/>
<field name="tag_ids"/>
<template>
<t t-name="card">
<div class="oe_kanban_global_click">
<div class="o_kanban_image">
<field name="image_preview" widget="image" options="{'size': [90, 90]}"/>
</div>
<div class="oe_kanban_details">
<strong><field name="name"/></strong>
<div><field name="media_type"/></div>
<div><field name="tag_ids" widget="many2many_tags"/></div>
</div>
</div>
</t>
</template>
</kanban>
</field>
</record>
<!-- Media Action -->
<record id="action_social_media" model="ir.actions.act_window">
<field name="name">Medya Kütüphanesi</field>
<field name="res_model">mymach.social.media</field>
<field name="view_mode">kanban,list,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Medya Kütüphanenize ilk dosyanızı ekleyin!
</p>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,139 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Top Level Menu -->
<menuitem id="menu_social_root"
name="MyMach Social"
web_icon="mymach_social_nextgen,static/description/icon.png"
sequence="50"/>
<!-- Dashboard (Default Home) -->
<menuitem id="menu_social_dashboard"
name="Kontrol Paneli"
parent="menu_social_root"
action="action_social_dashboard_kanban"
sequence="10"/>
<!-- Calendar Action -->
<record id="action_social_calendar" model="ir.actions.act_window">
<field name="name">Takvim</field>
<field name="res_model">mymach.social.post</field>
<field name="view_mode">calendar,kanban,list,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Aylık Gönderi Planınız Burada Görünecek!
</p>
</field>
</record>
<!-- Sentiment Dashboard -->
<menuitem id="menu_social_sentiment"
name="Duygu Analizi"
parent="menu_social_root"
action="action_social_sentiment_dashboard"
sequence="12"/>
<!-- Inbox / Live Chat -->
<menuitem id="menu_social_inbox"
name="Gelen Kutusu (Sohbet)"
parent="menu_social_root"
action="action_social_inbox"
sequence="15"/>
<!-- Calendar Menu -->
<menuitem id="menu_social_calendar"
name="Takvim"
parent="menu_social_root"
action="action_social_calendar"
sequence="18"/>
<!-- Posts Menu -->
<menuitem id="menu_social_posts"
name="Gönderiler"
parent="menu_social_root"
action="action_social_post"
sequence="20"/>
<!-- Streams Menu -->
<menuitem id="menu_social_streams"
name="Akışlar (Streams)"
parent="menu_social_root"
action="action_social_stream"
sequence="25"/>
<!-- Campaigns Action -->
<record id="action_social_campaign" model="ir.actions.act_window">
<field name="name">Kampanyalar</field>
<field name="res_model">utm.campaign</field>
<field name="view_mode">list,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Pazarlama Kampanyalarınızı Buradan Yönetin
</p>
</field>
</record>
<!-- Campaigns Menu -->
<menuitem id="menu_social_campaigns"
name="Kampanyalar"
parent="menu_social_root"
action="action_social_campaign"
sequence="30"/>
<!-- Media Library Menu -->
<menuitem id="menu_social_media_library"
name="Medya Kütüphanesi"
parent="menu_social_root"
action="action_social_media"
sequence="35"/>
<!-- Visitors Action -->
<record id="action_social_visitor" model="ir.actions.act_window">
<field name="name">Ziyaretçiler (Tıklamalar)</field>
<field name="res_model">link.tracker.click</field>
<field name="view_mode">list,form</field>
<field name="context">{'create': False, 'edit': False}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Sosyal Medya Ziyaretçilerinizi (Tıklamaları) Buradan Takip Edin
</p>
</field>
</record>
<!-- Visitors Menu -->
<menuitem id="menu_social_visitors"
name="Ziyaretçiler"
parent="menu_social_root"
action="action_social_visitor"
sequence="40"/>
<!-- Configuration Menu -->
<menuitem id="menu_social_configuration"
name="Yapılandırma"
parent="menu_social_root"
sequence="100"/>
<!-- Accounts Menu (Under Configuration) -->
<menuitem id="menu_social_accounts_config"
name="Sosyal Hesaplar"
parent="menu_social_configuration"
action="action_social_account"
sequence="10"/>
<!-- Auto-Reply Rules Menu (Under Configuration) -->
<menuitem id="menu_social_auto_reply_config"
name="Otomatik Yanıt Kuralları"
parent="menu_social_configuration"
action="action_social_auto_reply"
sequence="20"/>
<!-- Auto-Reply Logs Menu (Under Configuration) -->
<menuitem id="menu_social_auto_reply_logs_config"
name="Otomatik Yanıt Geçmişi"
parent="menu_social_configuration"
action="action_social_auto_reply_log"
sequence="30"/>
</data>
</odoo>

View File

@@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Form View -->
<record id="view_social_post_form" model="ir.ui.view">
<field name="name">mymach.social.post.form</field>
<field name="model">mymach.social.post</field>
<field name="arch" type="xml">
<form string="Sosyal Medya Gönderisi">
<header>
<button name="action_open_ai_wizard" string="✨ AI ile İçerik Üret" type="object" class="btn-info" invisible="state != 'draft'"/>
<button name="action_open_ai_image_wizard" string="🎨 AI Görsel Üret" type="object" class="btn-primary" invisible="state != 'draft'"/>
<button name="action_request_approval" string="Onaya Gönder" type="object" invisible="state != 'draft'"/>
<button name="action_schedule" string="Planla" type="object" class="oe_highlight" invisible="state not in ('draft', 'waiting')" groups="mymach_social_nextgen.group_social_manager"/>
<button name="action_publish_now" string="Onayla ve Yayınla" type="object" class="btn-warning" invisible="state not in ('draft', 'waiting')" groups="mymach_social_nextgen.group_social_manager"/>
<button name="action_reset_to_draft" string="Taslağa Çevir" type="object" invisible="state == 'draft'" groups="mymach_social_nextgen.group_social_manager"/>
<field name="state" widget="statusbar" statusbar_visible="draft,waiting,scheduled,published"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="%(action_social_live_post)d" type="action" class="oe_stat_button" icon="fa-share-square-o" invisible="state not in ('published', 'failed')">
<field name="live_post_ids" widget="statinfo" string="Canlı Gönderiler"/>
</button>
<button name="action_view_visitors" type="object" class="oe_stat_button" icon="fa-mouse-pointer" invisible="state not in ('published', 'failed')">
<field name="click_count" widget="statinfo" string="Ziyaretçiler"/>
</button>
</div>
<div class="oe_title">
<h1>
<field name="name" placeholder="Gönderi Başlığı..."/>
</h1>
</div>
<group>
<group>
<field name="account_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
<field name="scheduled_date" invisible="state == 'published'"/>
<field name="published_date" invisible="state != 'published'"/>
</group>
<group>
<field name="media_ids" widget="many2many_binary" string="Görseller (Ekler)"/>
<field name="post_type" widget="radio" options="{'horizontal': true}"/>
<field name="utm_campaign_id"/>
<field name="product_id" string="E-Ticaret Ürünü" help="Seçilen ürünün satın alma linki ve fiyatı paylaşıma otomatik eklenecek."/>
</group>
</group>
<notebook>
<page string="İçerik">
<group>
<group>
<field name="content" widget="text" placeholder="Gönderinizi buraya yazın..." nolabel="1" colspan="2"/>
</group>
<group>
<field name="preview_html" widget="html" nolabel="1" colspan="2" readonly="1"/>
</group>
</group>
</page>
<page string="Hata Mesajı" invisible="state != 'failed'">
<field name="error_message"/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- List View -->
<record id="view_social_post_list" model="ir.ui.view">
<field name="name">mymach.social.post.list</field>
<field name="model">mymach.social.post</field>
<field name="arch" type="xml">
<list string="Sosyal Medya Gönderileri" decoration-info="state == 'scheduled'" decoration-success="state == 'published'" decoration-danger="state == 'failed'" decoration-warning="state == 'waiting'">
<field name="name"/>
<field name="account_ids" widget="many2many_tags"/>
<field name="scheduled_date"/>
<field name="published_date"/>
<field name="state" widget="badge"/>
</list>
</field>
</record>
<!-- Kanban View -->
<record id="view_social_post_kanban" model="ir.ui.view">
<field name="name">mymach.social.post.kanban</field>
<field name="model">mymach.social.post</field>
<field name="arch" type="xml">
<kanban default_group_by="state" class="o_kanban_small_column" quick_create="false">
<field name="name"/>
<field name="state"/>
<field name="color"/>
<field name="scheduled_date"/>
<field name="account_ids"/>
<template>
<t t-name="card">
<div class="oe_kanban_global_click">
<div class="oe_kanban_content">
<div class="o_kanban_record_title">
<strong><field name="name"/></strong>
</div>
<div class="o_kanban_record_bottom mt-2">
<div class="oe_kanban_bottom_left">
<field name="account_ids" widget="many2many_tags"/>
</div>
<div class="oe_kanban_bottom_right">
<field name="scheduled_date"/>
</div>
</div>
</div>
</div>
</t>
</template>
</kanban>
</field>
</record>
<!-- Calendar View -->
<record id="view_social_post_calendar" model="ir.ui.view">
<field name="name">mymach.social.post.calendar</field>
<field name="model">mymach.social.post</field>
<field name="arch" type="xml">
<calendar string="Sosyal Medya Gönderileri" date_start="scheduled_date" color="state" mode="month" quick_create="0">
<field name="name"/>
<field name="account_ids"/>
<field name="state" filters="1"/>
</calendar>
</field>
</record>
<!-- Action -->
<record id="action_social_post" model="ir.actions.act_window">
<field name="name">Gönderiler</field>
<field name="res_model">mymach.social.post</field>
<field name="view_mode">kanban,list,calendar,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
İlk Sosyal Medya Gönderinizi Oluşturun!
</p>
<p>
İçeriklerinizi hazırlayın, medyalarınızı ekleyin ve planlayın.
</p>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Social Dashboard Kanban View -->
<record id="view_social_stream_post_kanban_dashboard" model="ir.ui.view">
<field name="name">mymach.social.stream.post.kanban.dashboard</field>
<field name="model">mymach.social.stream.post</field>
<field name="arch" type="xml">
<kanban class="o_social_dashboard_kanban" default_group_by="stream_id" create="0">
<field name="author_name"/>
<field name="published_date"/>
<field name="message"/>
<field name="likes_count"/>
<field name="comments_count"/>
<field name="shares_count"/>
<field name="stream_id"/>
<templates>
<t t-name="card">
<div t-attf-class="oe_kanban_global_click o_kanban_record">
<!-- Author & Time Info -->
<div class="o_social_author_section">
<div class="o_social_author_avatar d-flex align-items-center justify-content-center text-white">
<i class="fa fa-user"/>
</div>
<div class="d-flex flex-column">
<span class="o_social_author_name"><field name="author_name"/></span>
<span class="o_social_time"><field name="published_date"/></span>
</div>
</div>
<!-- Post Content -->
<div class="o_social_message_text">
<field name="message"/>
</div>
<!-- Actions / Metrics -->
<div class="o_social_actions">
<div title="Beğeniler">
<i class="fa fa-heart-o me-1"/> <field name="likes_count"/>
</div>
<div title="Yorumlar">
<i class="fa fa-comment-o me-1"/> <field name="comments_count"/>
</div>
<div title="Paylaşımlar">
<i class="fa fa-share-square-o me-1"/> <field name="shares_count"/>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- Dashboard Action -->
<record id="action_social_dashboard_kanban" model="ir.actions.act_window">
<field name="name">Kontrol Paneli</field>
<field name="res_model">mymach.social.stream.post</field>
<field name="view_mode">kanban,form</field>
<field name="view_id" ref="view_social_stream_post_kanban_dashboard"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Harika Sosyal Medya Kontrol Panelinize Hoşgeldiniz!
</p>
<p>
Burada yapılandırdığınız akışlar, ekranınızda sütunlar halinde listelenecektir.
</p>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Stream Kanban View -->
<record id="view_social_stream_kanban" model="ir.ui.view">
<field name="name">mymach.social.stream.kanban</field>
<field name="model">mymach.social.stream</field>
<field name="arch" type="xml">
<kanban class="o_kanban_dashboard" create="1">
<field name="name"/>
<field name="account_id"/>
<field name="stream_type"/>
<field name="search_keyword"/>
<template>
<t t-name="card">
<div class="oe_kanban_global_click">
<div class="o_kanban_record_title">
<strong><field name="name"/></strong>
</div>
<div><field name="account_id"/></div>
<div><field name="stream_type"/></div>
<div t-if="record.search_keyword.raw_value" class="text-info">
<i class="fa fa-search" title="Aranan Kelime"/> <field name="search_keyword"/>
</div>
</div>
</t>
</template>
</kanban>
</field>
</record>
<!-- Stream Form View -->
<record id="view_social_stream_form" model="ir.ui.view">
<field name="name">mymach.social.stream.form</field>
<field name="model">mymach.social.stream</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name"/>
<field name="account_id"/>
<field name="stream_type"/>
<field name="search_keyword" invisible="stream_type != 'keyword'" required="stream_type == 'keyword'"/>
</group>
<notebook>
<page string="Gönderiler ve Yorumlar">
<field name="stream_post_ids">
<list string="Akış Gönderileri">
<field name="author_name"/>
<field name="message"/>
<field name="likes_count"/>
<field name="comments_count"/>
<button name="action_create_lead" type="object" string="CRM Fırsatı Yarat" icon="fa-star" class="btn-success"/>
</list>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- Action -->
<record id="action_social_stream" model="ir.actions.act_window">
<field name="name">Akışlar (Streams)</field>
<field name="res_model">mymach.social.stream</field>
<field name="view_mode">kanban,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
İlk Sosyal Medya Akışınızı Ekleyin!
</p>
<p>
Sosyal medya hesaplarınıza gelen yorum ve mesajları buradan canlı takip edebilirsiniz.
</p>
</field>
</record>
<!-- Live Chat (Inbox) Action -->
<record id="action_social_inbox" model="ir.actions.act_window">
<field name="name">Canlı Sohbet (Gelen Kutusu)</field>
<field name="res_model">mymach.social.stream.post</field>
<field name="view_mode">list,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Sosyal Medya Mesajlarınız Burada Toplanacak!
</p>
</field>
</record>
<!-- Live Chat List View -->
<record id="view_social_stream_post_list" model="ir.ui.view">
<field name="name">mymach.social.stream.post.list</field>
<field name="model">mymach.social.stream.post</field>
<field name="arch" type="xml">
<list string="Gelen Mesajlar" decoration-info="True" create="0">
<field name="published_date"/>
<field name="author_name" string="Gönderen"/>
<field name="account_id" string="Hesap"/>
<field name="message_type" widget="badge" decoration-info="message_type == 'direct_message'" decoration-muted="message_type == 'comment'"/>
<field name="message" string="Mesaj İçeriği"/>
<field name="sentiment" widget="badge" decoration-success="sentiment == 'positive'" decoration-danger="sentiment == 'negative'" decoration-warning="sentiment == 'neutral'"/>
<button name="action_analyze_sentiment" type="object" string="Duyguyu Analiz Et" icon="fa-smile-o"/>
<button name="action_create_lead" type="object" string="Fırsat Yarat" icon="fa-star" class="btn-success"/>
</list>
</field>
</record>
<!-- Live Chat Form View -->
<record id="view_social_stream_post_form" model="ir.ui.view">
<field name="name">mymach.social.stream.post.form</field>
<field name="model">mymach.social.stream.post</field>
<field name="arch" type="xml">
<form string="Mesaj Detayı" create="0">
<header>
<button name="action_suggest_reply" type="object" string="✨ AI ile Yanıt Öner" class="btn-info"/>
<button name="action_analyze_sentiment" type="object" string="Duyguyu Analiz Et"/>
<button name="action_create_lead" type="object" string="CRM Fırsatı Yarat" class="oe_highlight"/>
</header>
<sheet>
<group>
<group>
<field name="author_name" readonly="1"/>
<field name="account_id" readonly="1"/>
<field name="published_date" readonly="1"/>
<field name="message_type" widget="badge" decoration-info="message_type == 'direct_message'" decoration-muted="message_type == 'comment'"/>
<field name="sentiment" widget="badge" decoration-success="sentiment == 'positive'" decoration-danger="sentiment == 'negative'" decoration-warning="sentiment == 'neutral'"/>
</group>
<group>
<field name="likes_count" readonly="1"/>
<field name="comments_count" readonly="1"/>
<field name="shares_count" readonly="1"/>
</group>
</group>
<notebook>
<page string="Mesaj İçeriği">
<field name="message" widget="text" readonly="1"/>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,3 @@
from . import social_ai_wizard
from . import social_ai_image_wizard
from . import social_ai_reply_wizard

View File

@@ -0,0 +1,43 @@
from odoo import models, fields, api
from odoo.exceptions import UserError
import logging
_logger = logging.getLogger(__name__)
class SocialAIImageWizard(models.TransientModel):
_name = 'mymach.social.ai.image.wizard'
_description = 'AI Image Generator Wizard'
post_id = fields.Many2one('mymach.social.post', string='Post', required=True)
image_prompt = fields.Text(string='Görsel Tarifi', required=True,
default='Örn: Masada duran şık bir kahve fincanı, arka planda gün batımı.')
def action_generate_image(self):
self.ensure_one()
try:
import requests
import urllib.parse
import base64
encoded_prompt = urllib.parse.quote(self.image_prompt)
# Pollinations AI generates images for free without API keys.
pollinations_url = f"https://image.pollinations.ai/prompt/{encoded_prompt}?width=1024&height=1024&nologo=true"
img_data = requests.get(pollinations_url, timeout=30).content
encoded_image = base64.b64encode(img_data)
media_record = self.env['ir.attachment'].create({
'name': f"AI_Generated_Image.png",
'datas': encoded_image,
'res_model': 'mymach.social.post',
'res_id': self.post_id.id,
'type': 'binary',
})
self.post_id.write({'media_ids': [(4, media_record.id)]})
except Exception as e:
_logger.error(f"Image Generation Error: {str(e)}")
raise UserError(f"Görsel üretilirken hata oluştu: {str(e)}")

View File

@@ -0,0 +1,141 @@
from odoo import models, fields, api
from odoo.exceptions import UserError
import logging
_logger = logging.getLogger(__name__)
class SocialAIReplyWizard(models.TransientModel):
_name = 'mymach.social.ai.reply.wizard'
_description = 'AI Reply Generator Wizard'
stream_post_id = fields.Many2one('mymach.social.stream.post', string='Mesaj', required=True)
customer_message = fields.Text(related='stream_post_id.message', string="Müşteri Mesajı")
suggested_reply = fields.Text(string='Üretilen Yanıt')
ai_provider = fields.Selection([
('gemini', 'Google Gemini'),
('openai', 'OpenAI (ChatGPT)'),
('claude', 'Anthropic (Claude)'),
('grok', 'xAI (Grok)')
], string='Yapay Zeka Sağlayıcısı', default='gemini', required=True)
def action_generate_reply(self):
self.ensure_one()
system_instruction = "Sen profesyonel bir müşteri temsilcisisin. Gelen sosyal medya mesajına uygun, kibar, çözüm odaklı ve kurumsal bir yanıt önerisi oluştur. Kısa ve net ol."
user_prompt = f"Müşteri Mesajı: {self.customer_message}\n\nLütfen bu mesaja profesyonel bir yanıt üret."
try:
import requests
import json
generated_text = ""
if self.ai_provider == 'gemini':
api_key = self.env['ir.config_parameter'].sudo().get_param('mymach_social.gemini_api_key')
if not api_key: raise UserError("Google Gemini API Anahtarı eksik!")
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={api_key}"
payload = {
"system_instruction": {"parts": [{"text": system_instruction}]},
"contents": [{"parts": [{"text": user_prompt}]}],
"generationConfig": {"temperature": 0.5, "maxOutputTokens": 200}
}
response = requests.post(url, headers={'Content-Type': 'application/json'}, json=payload)
if response.status_code != 200: raise UserError(f"Gemini API Hatası: {response.text}")
generated_text = response.json().get('candidates', [{}])[0].get('content', {}).get('parts', [{}])[0].get('text', '')
elif self.ai_provider == 'openai':
api_key = self.env['ir.config_parameter'].sudo().get_param('mymach_social.openai_api_key')
if not api_key: raise UserError("OpenAI API Anahtarı eksik!")
url = "https://api.openai.com/v1/chat/completions"
headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {api_key}'}
payload = {
"model": "gpt-4o",
"messages": [
{"role": "system", "content": system_instruction},
{"role": "user", "content": user_prompt}
],
"temperature": 0.5,
"max_tokens": 200
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code != 200: raise UserError(f"OpenAI API Hatası: {response.text}")
generated_text = response.json().get('choices', [{}])[0].get('message', {}).get('content', '')
elif self.ai_provider == 'claude':
api_key = self.env['ir.config_parameter'].sudo().get_param('mymach_social.claude_api_key')
if not api_key: raise UserError("Claude API Anahtarı eksik!")
url = "https://api.anthropic.com/v1/messages"
headers = {
'x-api-key': api_key,
'anthropic-version': '2023-06-01',
'content-type': 'application/json'
}
payload = {
"model": "claude-3-5-sonnet-20240620",
"system": system_instruction,
"messages": [{"role": "user", "content": user_prompt}],
"max_tokens": 200,
"temperature": 0.5
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code != 200: raise UserError(f"Claude API Hatası: {response.text}")
generated_text = response.json().get('content', [{}])[0].get('text', '')
elif self.ai_provider == 'grok':
api_key = self.env['ir.config_parameter'].sudo().get_param('mymach_social.grok_api_key')
if not api_key: raise UserError("Grok API Anahtarı eksik!")
url = "https://api.x.ai/v1/chat/completions"
headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {api_key}'}
payload = {
"model": "grok-2",
"messages": [
{"role": "system", "content": system_instruction},
{"role": "user", "content": user_prompt}
],
"temperature": 0.5,
"max_tokens": 200
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code != 200: raise UserError(f"Grok API Hatası: {response.text}")
generated_text = response.json().get('choices', [{}])[0].get('message', {}).get('content', '')
if not generated_text:
raise UserError(f"{self.ai_provider} boş bir yanıt döndürdü.")
self.suggested_reply = generated_text.strip()
return {
'type': 'ir.actions.act_window',
'res_model': 'mymach.social.ai.reply.wizard',
'res_id': self.id,
'view_mode': 'form',
'target': 'new',
}
except Exception as e:
_logger.error(f"AI API Error: {str(e)}")
raise UserError(f"AI ile iletişim kurulamadı: {str(e)}")
def action_send_reply(self):
self.ensure_one()
if not self.suggested_reply:
raise UserError("Gönderilecek bir yanıt yok.")
# In a real app, this would call the Meta Graph API to post the reply to the comment/message.
# For our NextGen simulation, we will just log it or simulate it.
# In this demo, let's just close the wizard and show a success notification.
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': 'Başarılı',
'message': 'Mesaj başarıyla müşteriye iletildi!',
'type': 'success',
'sticky': False,
}
}

View File

@@ -0,0 +1,168 @@
from odoo import models, fields, api
from odoo.exceptions import UserError
import logging
_logger = logging.getLogger(__name__)
class SocialAIWizard(models.TransientModel):
_name = 'mymach.social.ai.wizard'
_description = 'AI Content Generator Wizard'
post_id = fields.Many2one('mymach.social.post', string='Post', required=True)
prompt = fields.Text(string='Gönderi Konusu', required=True,
default='Örn: Yeni çıkardığımız CNC makinesinin hız ve hassasiyet özelliklerini anlatan bir tanıtım gönderisi.')
tone = fields.Selection([
('professional', 'Profesyonel'),
('casual', 'Günlük ve Samimi'),
('humorous', 'Eğlenceli / Esprili'),
('formal', 'Resmi'),
], string='Ses Tonu', default='professional', required=True)
include_hashtags = fields.Boolean(string='Trend Hashtag\'ler Eklensin', default=True)
# Free Image Generation Fields
generate_image = fields.Boolean(string='Görsel Üret (Yapay Zeka - Ücretsiz)', default=False)
image_prompt = fields.Text(string='Görsel Tarifi', help='Eğer boş bırakılırsa, Gönderi Konusu kullanılacaktır.')
ai_provider = fields.Selection([
('gemini', 'Google Gemini'),
('openai', 'OpenAI (ChatGPT)'),
('claude', 'Anthropic (Claude)'),
('grok', 'xAI (Grok)'),
('none', 'Hiçbiri (Sadece Görsel Üret veya Manuel)')
], string='Yapay Zeka Sağlayıcısı', default='gemini', required=True)
def action_generate_content(self):
self.ensure_one()
# --- 1. Handle Text Generation if not 'none' ---
generated_text = ""
if self.ai_provider != 'none':
tone_map = dict(self._fields['tone'].selection).get(self.tone)
system_instruction = f"Sen uzman bir sosyal medya yöneticisisin. Verilen konuya göre Türkçe, etkileyici ve ilgi çekici bir sosyal medya gönderisi yaz. Tonlama: {tone_map}."
if self.include_hashtags:
system_instruction += " Metnin sonuna konuyla ilgili 3-5 adet trend hashtag ekle."
user_prompt = f"Konu: {self.prompt}\n\nLütfen bu konuya göre sosyal medya içeriğini üret."
try:
import requests
import json
if self.ai_provider == 'gemini':
api_key = self.env['ir.config_parameter'].sudo().get_param('mymach_social.gemini_api_key')
if not api_key: raise UserError("Google Gemini API Anahtarı eksik!")
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={api_key}"
payload = {
"system_instruction": {"parts": [{"text": system_instruction}]},
"contents": [{"parts": [{"text": user_prompt}]}],
"generationConfig": {"temperature": 0.7, "maxOutputTokens": 400}
}
response = requests.post(url, headers={'Content-Type': 'application/json'}, json=payload)
if response.status_code != 200: raise UserError(f"Gemini API Hatası: {response.text}")
generated_text = response.json().get('candidates', [{}])[0].get('content', {}).get('parts', [{}])[0].get('text', '')
elif self.ai_provider == 'openai':
api_key = self.env['ir.config_parameter'].sudo().get_param('mymach_social.openai_api_key')
if not api_key: raise UserError("OpenAI API Anahtarı eksik!")
url = "https://api.openai.com/v1/chat/completions"
headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {api_key}'}
payload = {
"model": "gpt-4o",
"messages": [
{"role": "system", "content": system_instruction},
{"role": "user", "content": user_prompt}
],
"temperature": 0.7,
"max_tokens": 400
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code != 200: raise UserError(f"OpenAI API Hatası: {response.text}")
generated_text = response.json().get('choices', [{}])[0].get('message', {}).get('content', '')
elif self.ai_provider == 'claude':
api_key = self.env['ir.config_parameter'].sudo().get_param('mymach_social.claude_api_key')
if not api_key: raise UserError("Claude API Anahtarı eksik!")
url = "https://api.anthropic.com/v1/messages"
headers = {
'x-api-key': api_key,
'anthropic-version': '2023-06-01',
'content-type': 'application/json'
}
payload = {
"model": "claude-3-5-sonnet-20240620",
"system": system_instruction,
"messages": [{"role": "user", "content": user_prompt}],
"max_tokens": 400,
"temperature": 0.7
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code != 200: raise UserError(f"Claude API Hatası: {response.text}")
generated_text = response.json().get('content', [{}])[0].get('text', '')
elif self.ai_provider == 'grok':
api_key = self.env['ir.config_parameter'].sudo().get_param('mymach_social.grok_api_key')
if not api_key: raise UserError("Grok API Anahtarı eksik!")
url = "https://api.x.ai/v1/chat/completions"
headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {api_key}'}
payload = {
"model": "grok-2",
"messages": [
{"role": "system", "content": system_instruction},
{"role": "user", "content": user_prompt}
],
"temperature": 0.7,
"max_tokens": 400
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code != 200: raise UserError(f"Grok API Hatası: {response.text}")
generated_text = response.json().get('choices', [{}])[0].get('message', {}).get('content', '')
if not generated_text:
raise UserError(f"{self.ai_provider} boş bir yanıt döndürdü.")
generated_text = generated_text.strip()
except Exception as e:
_logger.error(f"AI API Error: {str(e)}")
raise UserError(f"AI ile iletişim kurulamadı: {str(e)}")
# --- 2. Generate Image (Free Stable Diffusion via Pollinations) ---
media_record = False
if self.generate_image:
import urllib.parse
import base64
import requests
img_prompt = self.image_prompt if self.image_prompt else self.prompt
encoded_prompt = urllib.parse.quote(img_prompt)
pollinations_url = f"https://image.pollinations.ai/prompt/{encoded_prompt}?width=1024&height=1024&nologo=true"
try:
img_data = requests.get(pollinations_url, timeout=30).content
encoded_image = base64.b64encode(img_data)
media_record = self.env['ir.attachment'].create({
'name': f"AI_Generated_Image.png",
'datas': encoded_image,
'res_model': 'mymach.social.post',
'res_id': self.post_id.id,
'type': 'binary',
})
except Exception as img_e:
_logger.error(f"Stable Diffusion Image Error: {str(img_e)}")
raise UserError(f"Ücretsiz görsel üretilirken hata oluştu: {str(img_e)}")
# --- 3. Update the post ---
update_vals = {}
if generated_text:
update_vals['content'] = generated_text
if media_record:
update_vals['media_ids'] = [(4, media_record.id)]
if update_vals:
self.post_id.write(update_vals)

View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_social_ai_wizard_form" model="ir.ui.view">
<field name="name">mymach.social.ai.wizard.form</field>
<field name="model">mymach.social.ai.wizard</field>
<field name="arch" type="xml">
<form string="Yapay Zeka İçerik Üreticisi">
<group>
<field name="post_id" invisible="1"/>
<field name="ai_provider"/>
<field name="prompt" invisible="ai_provider == 'none'"/>
<field name="tone" invisible="ai_provider == 'none'"/>
<field name="include_hashtags" invisible="ai_provider == 'none'"/>
</group>
<group string="Görsel Üretimi (Ücretsiz Yapay Zeka)">
<field name="generate_image" widget="boolean_toggle"/>
<field name="image_prompt" invisible="generate_image == False"/>
<div class="alert alert-info" role="alert" invisible="not generate_image">
Görseller <b>Ücretsiz (Stable Diffusion)</b> altyapısı ile üretilecektir. Herhangi bir API anahtarı veya ek bakiye gerekmez!
</div>
</group>
<footer>
<button name="action_generate_content" string="Üret (Generate)" type="object" class="btn-primary"/>
<button string="İptal" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_social_ai_wizard" model="ir.actions.act_window">
<field name="name">AI ile İçerik Üret</field>
<field name="res_model">mymach.social.ai.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<!-- AI Image Wizard -->
<record id="view_social_ai_image_wizard_form" model="ir.ui.view">
<field name="name">mymach.social.ai.image.wizard.form</field>
<field name="model">mymach.social.ai.image.wizard</field>
<field name="arch" type="xml">
<form string="Yapay Zeka Görsel Üreticisi">
<group>
<field name="post_id" invisible="1"/>
<field name="image_prompt"/>
</group>
<div class="alert alert-info" role="alert">
Görseller <b>Ücretsiz (Stable Diffusion)</b> altyapısı ile üretilecektir. Herhangi bir API anahtarı veya ek bakiye gerekmez!
</div>
<footer>
<button name="action_generate_image" string="Görsel Üret" type="object" class="btn-primary"/>
<button string="İptal" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<!-- AI Reply Wizard -->
<record id="view_social_ai_reply_wizard_form" model="ir.ui.view">
<field name="name">mymach.social.ai.reply.wizard.form</field>
<field name="model">mymach.social.ai.reply.wizard</field>
<field name="arch" type="xml">
<form string="AI Yanıt Asistanı">
<group>
<field name="stream_post_id" invisible="1"/>
<field name="ai_provider"/>
<field name="customer_message" readonly="1"/>
<field name="suggested_reply"/>
</group>
<footer>
<button name="action_generate_reply" string="AI Yanıt Üret" type="object" class="btn-info" invisible="suggested_reply != False"/>
<button name="action_send_reply" string="Yanıtı Gönder" type="object" class="btn-success" invisible="not suggested_reply"/>
<button string="Kapat" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
</odoo>